clear 清除浮动(添加空div法)在浮动元素下方添加空 div,并给该元素写 CSS 样式 {clear:both; height:0; overflow:hidden;}
给浮动元素父级设置高度
父级同时浮动(需要给父级同级元素添加浮动)
父级设置成 inline-block
给父级添加 overflow: hidden
万能清除法 after 伪类 清浮动(现在主流方法,推荐使用)
具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素。
在常规流和 float 里面有效,不能包着脱离常规流的定位元素。
BFC 触发条件
根元素
position: absolute/fixed
display: inline-block / table
浮动元素
ovevflow 不为 visible
应用:
子元素浮动父元素高度塌陷,可以把父元素设置成 BFC
元素浮动后发生重叠,把其中一个设置成 BFC
行内元素:span、img、button、input、b、q、i、a、em、label
块元素:div、p、h1-h6、ul、ol、dl、li、header、footer、aside、section、article、form、table
区别:行内元素设置 width,height 属性无效,起边距作用的只 有 margin-left、margin-right、padding-left、padding-right,其它属性不会起边距效果(可以设置 line-height),设置 margin 和 padding 的上下不会对其他元素产生影响。块级元素可以设置 width,height 属性
水平:行内元素:父元素 text-align: center
块元素:宽度已知用 margin: auto,宽度未知:用 display: inline 变成行内元素后在父元素上设置 text-align: center
父元素 position: relative,子元素 position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%)
垂直:display: flex; align-item: center
grid 和 flex 布局对行内、块级元素都适用
Flex 意为弹性布局,任何一个容器都可以指定为 Flex 布局。行内元素也可以使用 Flex 布局。
flex-direction属性 该属性定义了子元素的排列方向
.box{
flex-direction: row | row-reverse | column | column-reverse;
}
flex-wrap属性 该属性称“轴线”的围绕方向
.box{
flex-wrap: nowrap | wrap | wrap-reverse;
}
flex-flow 属性: flex-direction 和 flex-wrap 的简写,默认值为 row nowrap
justify-content 属性 该属性定义了子元素内容在主轴上的对齐方式
.box{
justify-content: flex-start | flex-end | center | space-between | space-around;
}
align-items 属性 该属性定义了项目在交叉轴上如何对齐
.box{
align-items: flex-start | flex-end | center | baseline | stretch;
}
align-content 属性,该属性定义了多跟轴线的对齐方式,如果项目只有一根轴线,该属性不起作用
.box{
align-content: flex-start | flex-end | center | space-between | space-around | stretch;
}
Grid 布局与 Flex 布局有一定的相似性,都可以指定容器内部多个项目的位置。但是,它们也存在重大区别。
Flex 布局是轴线布局,只能指定"项目"针对轴线的位置,可以看作是一维布局。Grid 布局则是将容器划分成"行"和"列",产生单元格,然后指定"项目所在"的单元格,可以看作是二维布局。
https://www.ruanyifeng.com/blog/2019/03/grid-layout-tutorial.html
一般只有用到需要表格展示的数据才用 table,而且 table 布局代码量较大,页面可读性差
display: none | z-index: -9999(只能在定位元素上生效) | opacity:0 | position: absolute; left: -9999; top: -9999
!important - 内联 - id - 类 - 标签 - 通配符 - 继承
em:参考的是父元素的 font-size,具有继承的特点,如果自身定义了 font-size 则按自身来计算(浏览器默认字体是16px),整个页面内 1em 不是一个固定的值
rem:相对于根元素 html,可以设置根元素 html 的 font-size 为 10px,则 1.2em 就是 12px;
vw:css3 新单位,view width 的缩写,是指可视窗口的宽高,假如宽度是 1200px,则 10vw 就是120px;举个例子:浏览器宽度1200px, 1 vw = 1200px / 100 = 12 px。
Vh:类似 vw,指的是可视窗口的高度。
Vm:相对于视口的宽度或高度中较小的那个,其中最小的单位被均分为100个单位,举个例子:浏览器高度 900px,宽度 1200px,取最小的浏览器高度,1vm = 900px / 100 = 9 px
语义化就是选择与语义相符合的标签,使代码语义化,这样不仅便于开发者进行阅读,同时也能维护和写出更优雅的代码,还能够让搜索引擎和浏览器等工具更好地解析。
通俗的讲语义化就是让正确的标签做正确的事情,比如段落用 p 标签,头部用 header 标签,主要内容用 main 标签,侧边栏用 aside 标签等等。
将视口大小设置为可视区域的大小。
content-box 和 border-box 的区别:计算最大尺寸时是否包含边距,border-box 最大尺寸是包含了边距的(width:100, content(80) + border(10) = 100),content-box 最大尺寸是不包含边距(width: 100, content(100) + border(10) = 120)
原理:响应式开发一套界面,通过检测视口分辨率,针对不同客户端在客户端做代码处理,来展现不同的布局和内容
方案
媒体查询:@media 可以针对不同的屏幕尺寸设置不同的样式,当你重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。
百分比布局:通过百分比单位,可以使得浏览器中组件的宽和高随着浏览器的高度的变化而变化,从而实现响应式的效果。Bootstrap 里面的栅格系统就是利用百分比来定义元素的宽高,CSS3 支持最大最小高,可以将百分比和 max(min) 一起结合使用来定义元素在不同设备下的宽高。
rem 布局:rem 是 CSS3 新增的单位,rem 单位都是相对于根元素 html 的 font-size 来决定大小的。当页面的 size 发生变化时,只需要改变 font-size 的值,那么以 rem 为固定单位的元素的大小也会发生响应的变化(而 em 是相对于父元素的)。
BOM 是浏览器对象模型,用来获取或设置浏览器的属性、行为,例如:新建窗口、获取屏幕分辨率、浏览器版本号等。
DOM 是文档对象模型,用来获取或设置文档中标签的属性,例如获取或者设置 input 表单的 value 值。
都是不存在于 DOM 文档中的虚拟元素,虽然逻辑上存在,但并不实际存在于 DOM 树中。
伪类的效果可以通过添加实际的类来实现。
伪元素的效果可以通过添加实际的元素来实现。
所以它们的本质区别就是是否抽象创造了新元素。
伪类只能使用 “:”,伪元素可以使用 “:” 也可以使用 “::”。
轮播图基本都是 ul 盒子里面的 li 元素,首先获取第一个 li 元素和最后一个 li 元素,
克隆第一个 li 元素,和最后一个 li 元素,分别插入到 last li 的后面和 first li 的前面,
然后监听滚动事件,如果滑动距离超过 x 或 -x,让其实现跳转下一张图或者跳转上一张,(此处最好设置滑动距离),
然后在滑动最后一张实现最后一张和克隆第一张的无缝转换,当到克隆的第一张的时候停下的时候,让其切入真的第一张,则实现无线滑动。向前滑动同理。
https://juejin.cn/post/6844904122643120141
https://segmentfault.com/a/1190000013424772
左右两栏固定宽度,中间部分自适应的三栏布局。
https://juejin.cn/post/6844903817104850952
什么是浮动元素:浮动元素同时处于常规流内和流外的元素。其中块级元素认为浮动元素不存在,而浮动元素会影响行内元素的布局,浮动元素会通过影响行内元素间接影响了包含块的布局。
常规流:页面上从左往右,从上往下排列的元素流,就是常规流。
脱离常规流:绝对定位,fixed 定位的元素有自己固定的位置,脱离了常规流。
包含块:一个元素离它最近的块级元素是它的包含块。
https://blog.csdn.net/qq_43569680/article/details/123750956
CSS 变量可以访问 DOM,创建局部或全局变量,使用 JS 和媒体查询来修改变量。全局变量可以在整个文档进行访问使用,局部变量只能在声明它的选择器内部使用。
基本:null、undefined、boolean、number、string、symbol、BigInt。
引用:Obect、Array、Function、Data。
基本数据类型指的是简单的数据段,是按值访问的,因为可以直接操作保存在变量中的实际值。引用数据类型指的是有多个值构成的对象,改变引用数据类型是操作对象在栈内存中的引用地址。
引用数据类型的值可以改变、可以添加属性和方法、赋值是对象引用。
null 是空对象引用(空指针),undefined 没有赋值的对象(未初始化的变量),值相等但类型不同。
String、Number 和 Boolean 为了方便我们操作,有对应的基本包装类型。
http://t.zoukankan.com/lessfish-p-4836101.html
对象中都有两个默认的属性,叫__proto__和 constructor, 指向当前对象的原型、当前对象的构造函数
函数独有一个 prototype 属性指向它的原型,因为函数也是一种对象,所以函数也拥有__proto__和 constructor 属性。(原型是 Function 的对象:Object, Number, Boolean, String, Array, RegExp, Date, Function, Error)。
__proto__属性的作用就是当访问一个对象的属性时,如果该对象内部不存在这个属性,那么就会去它的__proto__属性所指向的那个对象(原型)里找,一直找,直到__proto__属性的终点 null,再往上找就相当于在 null 上取值,会报错。通过__proto__属性将对象连接起来的这条链路即原型链。
所有的对象都有原型吗?
Object.create(null) 创建的对象没有原型。
全局作用域、函数作用域、块级作用域(存在 let 或 const 的作用域)
当需要使用函数或者变量时,如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这样一个查找过程形成的链条就叫做作用域链。
反过来一般情况下外层不能访问内层作用域的变量,会报错 xxx is not defined。
简单点说,对象是引用传递,基础类型是值传递,通过将基础类型包装可以以引用方式传递
按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本。修改形参的值并不会影响实参。(类似深拷贝对象)通过自定义函数实现深拷贝(递归遍历对象)
// 通过遍历拿到 source 中的所有属性,取出当前遍历到的属性对应的值,判断当前的取值是否是引用数据类型(对象、数组、函数,一般是对象嵌套),通过 sourceValue.constructor 拿到这个对象的构造函数的类型,然后新建这个对象或数组,再次调用深拷贝,将遍历到的属性的值拷贝给新建的对象或数组,如果不是引用数据类型,之前的属性拷贝即可
function deCopy(target, source){
for(let key in source){
let sourceValue = souce[key];
if(sourceValue instanceof Object){
let subTarget = new sourceValue.constructor;
deCopy(subTarget, sourceValue);
}else{
target[key] = sourceValue
}
}
}
如果是原始类型,无需继续拷贝,直接返回
如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
很容易理解,如果有更深层次的对象可以继续递归直到属性为原始类型。
function deCopy(target) {
if (typeof target instanceof Object) {
let deCopyTarget = {};
for (const key in target) {
deCopyTarget[key] = decopy(target[key]);
}
return deCopyTarget;
} else {
return target;
}
};
按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本。这意味着函数形参的值如果被修改,实参也会被修改。同时两者指向相同的值。(类似浅拷贝对象)
function copy(target) {
let cloneTarget = {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
};
按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的 BUG。按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低。两种传值方式都有各自的问题。可能很多人都是做后端的,所有会想到“引用传递”,然而实际上却不是,导致了问题的产生。
协议、域名、端口三者有一个不同就会引起跨域的错误问题
严格的说,浏览器并不是拒绝所有的跨域请求,实际上拒绝的是跨域的读操作。浏览器的同源限制策略是这样执行的:
通常浏览器允许进行跨域写操作(Cross-origin writes),如链接,重定向;
通常浏览器允许跨域资源嵌入(Cross-origin embedding),如 img、script 标签;
通常浏览器不允许跨域读操作(Cross-origin reads)。
解决:JSONP(已过时):通过创建 script 标签,其 src 指向非同源的 url,并传递一个 callback 参数作为函数名的函数的调用和一系列参数,页面接收到响应后执行回调并对数据进行处理。
CORS:服务端在 http header 设置 Access-Control-Allow-Origin 即可)。CORS 是一种机制,是 W3C 标准。它允许浏览器向跨源服务器,发出 XMLHttpRequest 或 Fetch 请求。并且整个 CORS 通信过程都是浏览器自动完成的,不需要用户参与。服务器实现 CORS 接口,浏览器支持这个功能即可实现。
Nginx 反向代理 https://blog.csdn.net/weixin_46872121/article/details/111700983
个人经验:
webpack 本来就是开发者模式,vue.config.js 配一下 devServer 的 proxy,原理是利用 http-proxy-middleware 这个http 代理中间件,在本地起一个 node 服务,实现请求转发给其他的服务器
当你有一个单独的 API 后端开发服务器,并且想要在同一个域上发送 API 请求时,则代理这些 url。看例子:
proxy: {
'/proxy': {
// 目标代理服务器地址
target: 'http://your_api_server.com',
// 允许跨域
changeOrigin: true,
pathRewrite: {
'^/proxy': ''
}
}
}
假设你主机名为 localhost:8080, 请求 API 的 url 是 http://your_api_server.com/user/list
‘/proxy’:如果点击某个按钮,触发请求 API 事件,这时请求 url 是http://localhost:8080/proxy/user/list
changeOrigin:如果 true ,那么 http://localhost:8080/proxy/user/list 变为 http://your_api_server.com/proxy/user/list。但还不是我们要的 url 。
pathRewrite:重写路径。匹配 /proxy ,然后变为’’ ,那么 url 最终为 http://your_api_server.com/user/list。
https://www.cnblogs.com/zhilili/p/14738262.html
方法一:倒进集合再倒出来
[...new Set([1, 2, 2, 3, 4, 5, 5])]
//[1, 2, 3, 4, 5]
方法二:indexOf()
方法三:双重循环(先排序然后用 splice )
一种特殊的数据类型,定义不可更改,适合用来作为属性名标识独一无二的对象
//创建一个symbol类型的值
const s = Symbol();
console.log(typeof s);//"symbol"
用作对象属性、模拟类的私有方法
generator 函数返回一个遍历器对象
遍历器对象 每次调用 next 方法返回有 value 和 done 两个属性的对象
generator 函数 yield 后面的表达式即为 返回对象 value 属性的值
遍历器对象每执行一次 next() 都只执行了 generator 函数内部部分代码,遇到 yield 本次执行就结束了。
写法跟 generator 很像,就是将星号替换成 async,将 yield 替换成 await。async 是加在函数前的修饰符,无论 async 函数有无 await 操作,其总是返回一个 Promise。所以 async 函数可以直接接 then,返回值就是 then 方法传入的参数。如果 await 等到的不是一个 promise 对象,那跟着的表达式的运算结果就是它等到的东西。
async/await 是 generator 的语法糖,就是将 Generator 函数和自动执行器,包装在一个函数里。awiat 相当于 Pormise.then()。try…catch 相当于替代了 Promise 的 catch
防抖是事件触发 n 秒后执行回调,如果在这 n 秒内又被触发,则重新计时。
使用场景:
提交按钮时防止多次提交,只执行最后一次提交。
搜索框搜索输入。只需用户最后一次输入完,再发送请求。
手机号、邮箱验证输入检测。
窗口大小 resize。只需窗口调整完成后,计算窗口大小。防止重复渲染。
实现
function debounce(f,time){
let timeId = null
return function(...argments){
clearTimeout(timeId)
var timeId = setTimeout(f(argments),time)
}
}
节流是 n 秒内只能触发一次函数,如果 n 秒内多次触发,只有一次生效。
使用场景:
拖拽在固定时间内只执行一次,防止超高频率触发位置变动;缩放时监控浏览器 resize。
滚动加载,加载更多或滚到底部监听。
搜索框,搜索联想功能
实现
function throttle(f, duration) {
var timerId
var lastRunTime = 0
return function(...args) {
clearTimeout(timerId)
var now = Date.now()
if (now - lastRunTime > duration) {
f(...args)
lastRunTime = now
} else {
timerId = setTimeout(() => {
f(...args)
lastRunTime = Date.now()
}, duration)
}
}
}
使用 Array.from()
使用 Array.prototype.slice.call()
使用 Array.prototype.forEach() 进行遍历并生成新的数组
转换后数组的长度由 length 属性决定。索引不连续时转换结果是连续的,会自动补位
const 和 let(在 for 循环中,使用 let 声明循环变量 i,当前的 i 只在本轮循环有效,每一次循环的 i 都是一个新的变量)https://juejin.cn/post/7171335481798426637
模板字符串
箭头函数
对象和数组解构
async/await
对象超类:ES6 允许在对象中使用 super 方法
for…of 和 for…in,for…of 用于遍历一个迭代器,如数组,for…in 用来遍历对象中的属性
ES6 中的类:ES6 中支持 class 语法,不过,ES6 的 class 不是新的对象继承模型,它只是原型链的语法糖表现形式。
函数的参数默认值
Spread / Rest 操作符
二进制和八进制字面量(这是啥
for in 是 ES5 标准,遍历的是 key(可遍历对象、数组或字符串的 key);for of 是 ES6 标准,遍历的是 value(可遍历对象、数组或字符串的 value)。
使用 for in 可以遍历数组,但是会存在以下问题:
语句和表达式的区别在于,语句是为了进行某种操作,一般情况下不需要返回值,而表达式都是为了得到返回值,一定会返回一个值(这里的值不包括undefined)。
例如:var a = 1 + 2 是语句
1 + 2 是表达式
我们把每一个 .js 文件都视为一个 块,模块内部有自己的作用域,不会影响到全局。并且,我们约定一些关键词来进行依赖声明和 API 暴露。而这些约定的关键词就是通过制定一些规范去进行规范的。比较有名模块化规范的是 CMD、AMD、CommonJS 和 ES6 Module。Webpack 是模块化工具。
将模板、样式和逻辑都抽象出来独立出来的做法称之为组件化。比如说,我们在开发 Button 组件的时候,不再需要分别在几个文件夹之间跳来跳去,去修改它们的模板、样式和逻辑。我们只需要在公共的 Button 组件的文件夹里修改就好了。Vue 和 React 也是组件化的框架。
https://segmentfault.com/a/1190000017466120
直接原因:ES6 中的 Object.prototype 没有实现 Symbol.iterator 属性。
对象的哪个属性先遍历,哪个属性后遍历是不确定的,需要开发者手动指定。
部署了遍历器接口的对象其实就是 ES6 里的 Map 结构,现在 ES6 中内置了数据结构 Map,所以很方便地实现了集合对象。
Map 和 Object 比较
Map 是干净的,只含有显示插入的键,而 Object上会有原型上的属性以及方法,ES5 之后可以说使用 Object.create(null) 来创建一个干净的对象(vuex 源码中大量使用)
Map 的键可以是任意的数据类型,包括基本的数据类型,对象以及函数,而 Object 只允许使用 Symbol 以及 String
Map 中的 key 是有序的,迭代的时候以其插入的顺序返回键值,而 Object 的键是无序的
Map 长度可以通过 size 方法来获取,而 Object 需要手动计算(Object.keys(obj).length)
Map 是可迭代的,Object 需要通过获取键来迭代
应用场景
当需要在单独的逻辑中访问属性或者元素的时候,应该使用 Object,例如:
var obj = {
id: 1,
name: "It's Me!",
print: function() {
return `Object Id: ${this.id}, with Name: ${this.name}`;
}
}
console.log(obj.print()); // Object Id: 1, with Name: It's Me.
// 以上操作不能用 Map 实现
JSON 直接支持 Object,但不支持 Map
Map 是纯粹的 hash,而 Object 还存在一些其他内在逻辑,所以在执行 delete 的时候会有性能问题。所以写入删除密集的情况应该使用 Map。
Map 会按照插入顺序保持元素的顺序,而 Object 做不到。
Map 在存储大量元素的时候性能表现更好,特别是在代码执行时不能确定 key 的类型的情况。
原型链继承:将父类的实例作为子类的原型
构造继承:使用 call()、apply() 或 bind() 方法继承父类构造函数中的属性
实例继承:为父类实例添加新特性,作为子类实例返回
组合继承:通过调用父类构造,继承父类的属性并保留传参的优点,然后通过将父类实例作为子类原型,实现函数复用
寄生组合继承:通过寄生方式,砍掉父类的实例属性,这样,在调用两次父类的构造的时候,就不会初始化两次实例方法/属性,避免的组合继承的缺点
三个方法的第一个参数都是要绑定的 this,call 的后续参数依次传入,apply 的参数放在一个数组中一起传入,call 和 apply 是立即执行函数。bind 返回一个函数并把它的 this 绑定为传入的第一个参数。
实现 bind:
function myBind(thisArg, ...fixedArgs){
let self = this
return function bound(...arguments){
fixedArgs.push(...arguments)
if(new.target === bound){
return new self(...fixedArgs)
}else{
return self.apply(thisArg,fixedArgs)
}
}
}
为什么要使用 bind?
作用域的问题,foo() {} 与 const foo = () => {} 里面的this作用域不一样,foo() {} 里面使用外部成员,需要 bind(this),直接使用的 this 的话,作用域仅在 foo 方法内部。
仅限于 const 定义基本数据类型
一个事件的生命周期有三个阶段:捕捉,目标,冒泡。
阻止事件冒泡,防止事件冒泡而带来不必要的错误和困扰。
这个方法就是:stopPropagation()
在外部节点添加一个事件处理器,并根据 target 属性判断事件来源,这样可以把内部共用的事件绑定到外部
浏览器的事件循环:
当某个宏任务执行完后,会查看是否有微任务队列。如果有,先执行微任务队列中的所有任务,如果没有,会读取宏任务队列中排在最前的任务,执行宏任务的过程中,遇到微任务,依次加入微任务队列。栈空后,再次读取微任务队列里的任务,依次类推。
Node的事件循环:
nodejs 是单线程执行的,同时它又是基于事件驱动的非阻塞 IO 编程模型,事件循环机制是实现这一特性的原理
异步操作时,将任务给到另外的线程(CPU 的其它核),异步事件触发之后,就会通知主线程,主线程执行相应事件的回调。
执行顺序
程序运行会从上至下依次执行所有的同步代码
在执行的过程中如果遇到异步代码会将异步代码放到事件循环中
当所有同步代码都执行完毕后,JS 会不断检测事件循环中的异步代码是否满足条件
一旦满足条件就执行满足条件的异步代码
事件循环中的任务被分为宏任务和微任务,是为了给高优先级任务一个插队的机会:微任务比宏任务有更高优先级。
综上事件循环为:同步 > 异步 微任务 > 宏任务
Promise 构造函数是和主线程代码一起同步执行的,then 方法是异步执行的
那么微任务和宏任务都有什么呢,简单总结下就是:
宏任务:整体代码 script、setTimeout、setInterval、DOM 事件、Ajax
微任务:原生 Promise 相关(如 Promise.then())、async/await、Node 环境下的 process.nextTick
注意:
历史原因,JS 这门语言在创立时,多进程多线程的架构并不流行,硬件支持不够好;
多线程的复杂性,多线程操作需要加锁,编码的复杂性会增高;
如果同时操作 DOM ,在多线程不加锁的情况下,会导致 DOM 渲染的结果不可预期。
由于 JS 可以操作 DOM,如果同时修改元素属性并同时渲染界面(即 JS 线程和 UI 线程同时运行),那么渲染线程前后获得的元素可能不一致。
Class 是 ES6 提供的更接近于传统语言的的写法,作为对象的模板。通过 class 关键字,可以定义类
class 写法只是一个语法糖,它只是让对象原型的写法更加清晰,更像面向对象编程的语法
全局作用域在页面打开时被创建,页面关闭时被销毁
编写在 script 标签中的变量和函数,作用域为全局,在页面的任意位置都可以访问到
在全局作用域中有全局对象 window,代表一个浏览器窗口,由浏览器创建,可以直接调用
全局作用域中声明的变量和函数会作为 window 对象的属性和方法保存
调用函数时,函数作用域被创建,函数执行完毕,函数作用域被销毁
每调用一次函数就会创建一个新的函数作用域,他们之间是相互独立的
在函数作用域中可以访问到全局作用域的变量,在函数外无法访问到函数作用域内的变量
在函数作用域中访问变量、函数时,会先在自身作用域中寻找,若没有找到,则会到函数的上一级作用域中寻找,一直到全局作用域
在函数作用域中也有声明提前的特性,对于变量和函数都起作用,此时函数作用域相当于一个小的全局作用域,详细声明提前请看声明提前部分
作用域链
函数对象和其它对象一样,拥有可以通过代码访问的属性和一系列仅供 JavaScript 引擎访问的内部属性。其中一个内部属性是[[Scope]],该内部属性包含了函数被创建的作用域中对象的集合,这个集合被称为函数的作用域链,它决定了哪些数据能被函数访问。
词法环境是一种标识符到变量的映射关系
在词法环境中有两个组成部分:
变量环境也是一个词法环境。他具有词法环境中所有的属性 在 ES6 中,LexicalEnvironment 和 VariableEnvironment 的区别在于前者用于存储函数声明和变量 let 和 const 绑定,而后者仅用于存储变量 var 绑定。
使用 let 和 const 声明的变量在词法环境创建时是未赋值初始值。而使用 var 定义的变量在变量环境创建时赋值为 undefined。这也就是为什么 const、let 声明的变量在声明前调用会报错,而 var 声明的变量不会。
https://github.com/mqyqingfeng/Blog/issues/4
https://github.com/mqyqingfeng/Blog/issues/5
https://github.com/mqyqingfeng/Blog/issues/6
https://github.com/mqyqingfeng/Blog/issues/8
当 JavaScript 代码执行一段可执行代码时,会创建对应的执行上下文。对于每个执行上下文,都有三个重要属性:
变量对象(Variable object,VO);
作用域链(Scope chain);
this(关于 this 指向问题,在上面推荐的深入系列也有讲从 ES 规范讲的,但是实在是难懂,对于应付面试来说以下这篇阮一峰的文章应该就可以了:JavaScript 的 this 原理 https://www.ruanyifeng.com/blog/2018/06/javascript-this.html)
共同点
只能遍历数组
都是循环遍历数组中的每一项
每一次执行匿名函数都支持三个参数,数组中的当前项 item,当前项的索引 index ,原始数组 input
匿名函数中的 this 都是指 window
forEach 方法跳出循环–通过 throw error() 抛出异常的方式跳出循环,通过 return 跳过当次循环
不同点
forEach 没有返回值,不能 return;map 有返回值,可以 return
this 指向什么取决函数的调用形式,而不取决于函数的在哪调用,也不取决于在哪定义。
全局环境下调用 this 指向 window(全局对象)
函数作为对象方法被调用(指向该对象)
通过 call、apply 和 bind 方法显式指定 this
作为构造函数调用(指向创建的对象实例)
纯函数形式调用,指向 window
this 永远不能被赋值,即 this 不能写在等号左边。
一张图片就是一个 img 标签,浏览器是否发起请求图片是根据 img 的 src 属性,所以实现懒加载的关键就是,在图片没有进入可视区域时,先不给 img 的 src 赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给 src 赋值。
实现懒加载有四个步骤,如下:
<img src="./example.jpg" loading="lazy">
引擎中有垃圾回收机制,调用函数的时候,系统会分配对应的空间给这个函数使用(空间大小的情况一般由这个函数的变量和形参决定),当函数使用完毕以后,这个内存空间要释放,还给系统,在函数内部声明的变量和形参是属于当前函数的内存空间的。
其实引擎虽然针对垃圾回收做了各种优化从而尽可能的确保垃圾得以回收,但并不是说我们就可以完全不用关心这块了,我们代码中依然要主动避免一些不利于引擎做垃圾回收操作,因为不是所有无用对象内存都可以被回收的,那当不再用到的对象内存,没有及时被回收时,我们叫它内存泄漏
标记清除:
大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。标记方式不定,可以是某个特殊位的反转或维护一个列表等。
垃圾收集器给内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上的标记的变量即为需要回收的变量,因为环境中的变量已经无法访问到这些变量。
引用计数:
以有没有其他对象引用到它为依据判断对象是否需要回收。如果没有引用指向该对象(引用计数为 0),对象将被垃圾回收机制回收。
使用 cdn,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
CDN 的原理:源网站内容分发至全国所有的节点,从而缩短用户查看对象的延迟,提高用户访问网站的响应速度与网站的可用性。
对那些可能对服务器数据产生副作用的 HTTP 请求方法,浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies 和 HTTP 认证相关数据)。
按照预检请求的理解,简单请求就是对服务器无副作用的请求。
可以使用 ES6 的解构语法来代替。
let func = (...args) => {
console.log('函数的参数是:', args);
}
func(1, 2, 3);
// 函数的参数是: [1, 2, 3]
异步函数返回一个 promise,一个 promise 对象代表一个异步操作结果。
一个 Promise 必然处于以下几种状态之一:
待定(pending): 初始状态,既没有被兑现,也没有被拒绝。
已成功(fulfilled): 意味着操作成功完成。
已拒绝(rejected): 意味着操作失败。
resolve() 可直接返回 Promise 成功对象,把一个普通对象转为 Promise 对象。手动实现 Promise 对象:
// 转成Promise对象
function foo() {
const obj = { name: "why" }
return new Promise((resolve) => {
resolve(obj)
})
}
foo().then(res => {
console.log("res:", res) // res: { name: 'why' }
})
Promise 通过修改状态的方式,在合适的时机触发相应状态的回调来达到处理异步的目的。Promise 通过 then() 链式调用,它接一个 Promise 对象的执行结果作为参数,return 一个返回值作为下一个 then 方法的参数,如果是 return 一个 Promise 对象,那么就需要判断它的状态。
Promise.race(values),返回一个在迭代器中遇到的第一个状态确定(settled)的 promise
Promise.race = function(values){
return new Promise((resolve,reject)=>{
for(let i = 0 ; i< values.length;i++){
Promise.resolve(values[i]).then(resolve,reject)
}
})
}
Promise.all(values),返回一个 promise 实例。如果迭代器中所有的 promise 参数状态都是 resolved, 则 promise 实例的状态为 resolved,其 [[PromiseValue]] 为每个参数的 [[PromiseValue]] 组成的数组;
如果参数中的 promise 有一个失败(rejected),此实例的状态为 rejected,其 [[PromiseValue]] 为是第一个失败 promise 的 [[PromiseValue]]
Promise.all = function(values) {
return new Promise((resolve, reject) => {
var result = []
var resolvedCount = 0
if (value.length == 0) {
resolve([])
return
}
for (let i = 0; i < values.length; i++) {
Promise.resolve(values[i]).then(val => {
result[i] = val
resolvedCount++
if (resolvedCount == values.length) {
resolve(result)
}
},reason => {
reject(reason)
})
}
})
}
Promise.allSettled(values)方法返回一个在所有给定的 promise 都已经 fulfilled 或 rejected 后的 promise,并带有一个对象数组,每个对象表示对应的 promise 结果。
在多个 promise 同时进行时我们很快会想到使用 Promise.all 来进行包装, 但是由于 Promise.all 的短路特性, 三个提交中若前面任意一个提交失败, 则后面的表单也不会进行提交了, 这就与我们需求不符合。
Promise.allSettled 跟 Promise.all 类似, 其参数接受一个 Promise 的数组, 返回一个新的 Promise, 唯一的不同在于, 其不会进行短路, 也就是说当 Promise 全部处理完成后我们可以拿到每个 Promise 的状态, 而不管其是否处理成功。
Promise.any。any 与 all 相反,接收一个 Promise 数组,数组中如有非 Promise 项,则此项当做成功,如果有一个Promise 成功,则返回这个成功结果,如果所有 Promise 都失败,则报错
搜索引擎优化,提高网站在搜索引擎里面的自然排名
fetch() 方法是比 XMLHttpRequest 更简洁的 Ajax 请求。fetch 是全局量 window 的一个方法,第一个参数为 URL。
// url (必须), options (可选)
fetch('/some/url', {
method: 'get'
}).then(function(response) {
}).catch(function(err) {
// 出错了;等价于 then 的第二个参数,但这样更好用更直观 :(
});
https://blog.csdn.net/qq_36754767/article/details/89645041
axios:
用于浏览器和 node.js 的基于 Promise 的 HTTP 客户端
1、从浏览器制作 XMLHttpRequests
2、让 HTTP 从 node.js 的请求
3、支持 Promise API
4、拦截请求和响应
5、转换请求和响应数据
6、取消请求
7、自动转换为 JSON 数据
8、客户端支持防止 XSRF
栈溢出(stack overflow)指使用过多的存储器时导致调用堆栈产生的溢出。堆栈溢出的产生是由于过多的函数调用,导致使用的调用堆栈大小超过事先规划的大小,覆盖其他存储器内的资料,一般在递归中产生。堆栈溢出很可能由无限递归(Infinite recursion)产生,但也可能仅仅是过多的堆栈层级。个人认为闭包一般不会造成栈溢出。
函数执行的时候,开辟的用来解析函数体的代码的新的栈内存,他也叫做私有作用域;
js 没有块级作用域,但是可以模拟,比如:
[]==[] 的结果是 false,因为每次使用 [] 都是新建一个数组对象,比较的时候比较的是它们的引用,尽管两两边看起来都是空数组但实际上从引用看是不相等的,[]==![] 的结果也是 false。如果想判断数组为空,可以判断 array.length === 0。
[]==![] 的结果是 true
型为 “”。
4. “” 会强制转换为 0。
5. 两侧都是 number 类型为 0,所以 0==0 为 true。
个人认为销毁了。
vue-router 默认使用 hash 模式,原理是 onhashchange 事件,基于 location.hash 来实现的。
window.addEventListener("hashchange", funcRef, false);
利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。
这两个方法应用于浏览器的历史记录站,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。这两个方法有个共同的特点:当调用他们修改浏览器历史记录栈后,虽然当前 URL 改变了,但浏览器不会刷新页面,这就为单页应用前端路由“更新视图但不重新请求页面”提供了基础。
较少用到。支持所有 JavaScript 运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式。
路由的配置一开始只有根目录 /,每写一层 children 就要写一层 router-view,否则组件不显示。每一个嵌套 children 的层级和 router-view 的层级都是一一对应的。
https://juejin.cn/post/6844903647806128135
回调函数
事件监听
发布订阅
Promise
在开发过程中,js 原生事件不足以满足开发需求,需要开发者自定义事件。
Events 可以使用 Event 构造函数创建,如下:
var event = new Event('build');
elem.addEventListener('build', function (e) { ... }, false);
elem.dispatchEvent(event) // 触发该事件
https://www.cnblogs.com/cangqinglang/p/9746650.html
在循环中使用,为 true 时跳过当前循环
柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。一个简单实现:
// 普通的add函数
function add(x, y) {
return x + y
}
// Currying后
function curryingAdd(x) {
return function (y) {
return x + y
}
}
add(1, 2) // 3
curryingAdd(1)(2) // 3
使用柯里化函数的好处
https://www.jianshu.com/p/2975c25e4d71
//因为new是关键字,我用函数的形式来实现,可以将构造函数和构造函数的参数传入
function myNew(f, ...args){
//1.创建一个空对象,并将对象的__proto__指向构造函数的 prototype 这里我两步一起做了
const obj = Object.create(f.prototype);
//2.将构造函数中的this指向obj,执行构造函数代码,获取返回值
const res = f.apply(obj, args);
//3.判断返回值类型
return res instanceof Object ? res : obj
}
会创建一个长度为 10,元素全为 undefined 的数组
map() 会跳过空位,但会保留这个值
其他方法:
forEach(), filter(), every() 和 some() 都会跳过空位。
join() 和 toString() 会将空位视为 undefined,而 undefined 和 null 会被处理成空字符串
http://www.javashuo.com/article/p-vwvhoxam-dq.html
单页面应用
https://blog.csdn.net/huangpb123/article/details/86183453
JS 在做数字计算的时候,使用 IEEE 754 二进制浮点运算,最大可以存储 53 位有效数字,于是大于 53 位后面的会全部截掉,将导致精度丢失。实际相加的是两个非常接近 0.1 和 0.2 的数字。
解决方案:转化为整数相加、使用库如 math.js
可以理解为是利用函数把运算过程封装起来,通过组合各种函数来计算结果。函数式编程的重点在于关心数据的映射
应用层:为应用程序提供网络服务
表示层:数据格式化、加密解密
会话层:解除、建立、管理会话连接
传输层:解除、建立、管理端到端的连接和通信细节
网络层:数据在网络中分组传输,IP 寻址和路由选择
数据链路层:控制网络层与物理层之间通信
物理层:以二进制数据形式在物理媒体上传输数据
GET 请求在浏览器刷新或者回退的时候是无副作用的。POST 的数据会被重新提交。
GET 会将数据存在浏览器的历史中,POST 不会。
GET 编码格式只能用 ASCII 码,POST 没有限制。
可见性,参数在 URL,用户可以看见,POST 的参数在 request body 中,不会被用户看见。
长度 get 参数一般限制为 url 可输入长度,post 参数无限制。
GET 一般用于获取数据,POST 一般用于修改和写入数据。
防止 TCP 会话攻击模拟 IP 和服务器建立连接。抓包只能发生在同一网络中,随机 ISN 能避免非同一网络的攻击
广域网的连接情况很复杂,可能连接波动但数据还没到服务器,报文不一定会按发送的时序到达目标,所以要 +1
初次登录的时候,前端调后调的登录接口,发送用户名和密码,后端收到请求,验证用户名和密码,验证成功,就给前端返回一个 token,和一个用户信息的值,前端拿到 token,将 token 储存到 Vuex 中,然后从 Vuex 中把 token 的值存入浏览器 Cookies 中。把用户信息存到 Vuex 然后再存储到 LocalStroage 中;
然后跳转到下一个页面,根据后端接口的要求,只要不登录就不能访问的页面需要在前端每次跳转页面时判断 Cookies 中是否有 token,没有就跳转到登录页,有就跳转到相应的页面,我们应该再每次发送 post/get 请求的时候应该加入 token,常用方法在项目 utils/service.js 中添加全局拦截器,将 token 的值放入请求头中
怎么把 token 放到 header?用:
headers: {'Authorization': token}
token 怎么设置过期时间?
由后端设置
讲一下 304 和重定向?
如果用户在正常操作的过程中,Token 过期失效了,要求用户重新登录。用户体验岂不是很糟糕?
使用 Refresh Token,它可以避免频繁的读写操作。这种方案中,服务端不需要刷新 Token 的过期时间,一旦 Token 过期,就反馈给前端,前端使用 Refresh Token 申请一个全新 Token 继续使用。
封装了一个 useRequest 函数,调接口传入参数后调用这个函数,传请求方法、后端路径和参数。然后里面封装了个 Promise 请求,用了 rxjs 的 toPromise()(将 Observer 返回的 Observable 对象转化成 Promise,Observable 是由 Observer(观察者)通过(Subscribe)订阅的方式被动接受各种形式的数据),正确就调用 .then() 返回 res,有错误就 .catch() 返回 err。
抽象语法树。
客户端使用 https 的 url 访问 web 服务器,要求与服务器建立 ssl 连接
tcp 连接建立后,客户端向服务端请求公钥,客户端拿到服务端返回的公钥后,用这个公钥加密一组对称密钥的信息,传输给服务端。后续 http 的通信过程,就用这套对称密钥加密解密。
http:超文本传输协议。基于文本,无状态,无连接。是一个客服端和服务器端请求和应答的标准(tcp),使浏览器更加高效,使网络传输减少。默认使用 80 端口
https:是以安全为目标的 HTTP 通道,简单讲是 HTTP 的安全版本,运行在 SSL/TLS 层上,而 SSL/TLS 层是运行在 TCP 层之上,通过 SSL 加密,要 CA 证书(要钱)。默认使用 443 端口
这里所谓的请求资源的一部分,也就是大家常说的断点续传
HTTP1.x 的解析是基于文本。HTTP2.0 的协议解析采用二进制格式
HTTP2.0 比 HTTP1.0 有多路复用,一个连接可以并发处理多个请求
header 压缩:HTTP1.x 的 header 带有大量信息,而且每次都要重复发送,HTTP2.0 使用 encoder 来减少需要传输的 header 大小
服务器推送:我们对支持 HTTP2.0 的 web server 请求数据的时候,服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源
多路复用:只通过一个 TCP 连接就可以传输所有的请求数据。解决了浏览器限制同一个域名下的请求数量的问题,同时也更容易实现全速传输
HTTP1.0 不支持长连接,因此一个 TCP 发送一个 HTTP 请求;
HTTP1.1 支持长连接,只要 TCP 连接不断开,可以一直发送 HTTP 请求,没有上限;
HTTP2.0 支持多用复用,一个 TCP 连接可以并发多个 HTTP 请求,也支持长连接,因此只要不断开 TCP 连接,HTTP 请求数可以没有上限地持续发送。
HTTP 协议是一种无状态协议,即每次服务端接收到客户端的请求时,都是一个全新的请求,服务器并不知道客户端的历史请求记录;Session 和 Cookie 的主要目的就是为了弥补 HTTP 的无状态特性。
Cookie: cookie 是存在浏览器的标识用户的方式,由服务端为每一个用户签发不同 session id 发送给浏览器并存储在cookie,下次访问会带上这个session id,服务端就知道这个访问是哪个用户了。
SessionStorage 和 LocalStorage:由支持本地存储的浏览器实现。添加属性即可实现存储。
Cookie: 默认是关闭浏览器后失效, 但是也可以设置过期时间
SessionStorage: 仅在当前会话(窗口)下有效,关闭窗口或浏览器后被清除,不能设置过期时间
LocalStorage: 除非被清除,否则永久保存
Cookie 容量限制:大小(4KB左右)和个数(20~50)
SessionStorage 和 LocalStorage 容量限制:大小(5M 左右)
localStorage 存满了怎么办?划分域名。各域名下的存储空间由各业务组统一规划使用;跨页面传数据:考虑单页应用、优先采用 url 传数据;最后的兜底方案:清掉别人的存储
Cookie 网络请求:在第一次请求时由服务端生成返回给客户端,每次都会携带在 HTTP 请求头中如果使用 cookie 保存过多数据会带来性能问题
SessionStorage 和 LocalStorage 网络请求:仅在浏览器中保存,不参与和服务器的通信
Cookie: 判断用户是否登录、保存上次查看的页面信息
sessionStorage: 表单数据、同一用户的不同页面、保存登录信息
LocalStorage: 购物车、一些需要长期保存在本地的数据
Express 中使用 res.cookie() 一个验证身份的字符串,网站在用户验证成功之后都会设置一个 cookie,只要 cookie 没有过期,用户就可以自由浏览这个网站的任意页面不需要再次登录
localStorage.setItem(item,value)
localStorage.getItem(item)
localStorage.removeItem(item)
sessionStorage 和 localStorage 用法一样,但是它只保存数据到浏览器关闭,不会触发 onstorage 事件
强缓存:服务器通知浏览器一个缓存时间,在缓存时间内的请求会直接使用缓存,不再执行比较缓存策略
协商缓存:让服务端判断客户端的资源是否更新的验证。提升缓存的复用率,将缓存信息中的 Etag 和 Last-Modified 通过请求发送给服务器,由服务器校验,返回 304 时直接使用缓存
http 协商缓存中:
Etag / lastModified 过程如下:
多久不重要,重要的是知道浏览器存在并发限制,不同浏览器的不同版本、不同 http 协议的并发限制数都不同
进程是 CPU 资源分配的最小单位,线程是 CPU 调度的最小单位。一个进程里面可以有多个线程,线程是共享了进程的上下文环境,的更为细小的 CPU 时间段。CPU 运行一个软件相当于打开一个了进程,执行该软件里面的 1 个功能相当于打开一个线程
https://juejin.cn/post/6844903919789801486
HTTPS 对称加密
服务器每次发送真实数据前,会先生成一把密钥传输(以明文方式传输密钥容易被劫持)给客户端,服务器给客户端发送的真实数据会先用这把密钥进行加密,客户端收到加密数据后再用密钥进行解密(客户端给服务器发送数据同理)
HTTPS 非对称加密
客户端和服务器都有两把密钥,一把公钥一把私钥(公钥加密的数据只有私钥才能解密,私钥加密的数据只有公钥才能解密),服务器在给客户端发送真实数据前,先用客户端明文传输给服务器的公钥进行加密,客户端收到后用自己的私钥进行解密,反之同理
HTTPS 对称加密 + 非对称加密
鉴于 HTTPS 非对称加密在加密时速度特别慢,可使用 HTTPS 对称加密 + 非对称加密(以非对称加密的方式传输对称加密密钥),接着就可使用对称加密的密钥传输数据。非对称加密之所以不安全是因为客户端不知道接收的公钥是否属于服务器
HTTPS 数字证书
核心在于证明客户端接收的公钥是属于服务器的,解决这个问题方法是使用数字证书(即找到一个大家都认可的认证中心 CA)
服务器在给客户端传输公钥的过程中,会将公钥+服务器个人信息通过 hash 算法生成信息摘要,为防止信息摘要被掉包服务器会用 CA 提供的私钥对信息摘要加密形成数字签名。最后还会将没有进行 hash 算法计算的服务器个人信息 + 公钥和数字签名合并在一起形成数字证书。
客户端拿到数字证书后,用 CA 提供的公钥对数字签名进行解密得到信息摘要,然后对数字证书中服务器个人信息+公钥进行hash 得到另一份信息摘要,两份信息摘要进行比对,若一样则是目标服务器,否则不是。
服务器会申请证书,客户端会内置证书。
304 状态码或许不应该认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。
客户端在请求一个文件的时候,发现自己缓存的文件有 Last Modified ,那么在请求中会包含 If Modified Since ,这个时间就是缓存文件的 Last Modified。因此,如果请求中包含 If Modified Since,就说明已经有缓存在客户端。服务端只要判断这个时间和当前请求的文件的修改时间就可以确定是返回 304 还是 200 。
强缓存命中返回 200(200 from cache)
常见状态码:
200 请求处理成功
204 请求处理成功
301 永久性重定向,表示本网页(资源)永久性转移到另一个地址。老 URL 返回一个 301 状态码并配合 location 让 location 的值等于新的 URL,最终进行跳转,之后浏览器会记住新的 URL,不会再访问 URL。
302 临时性重定向,暂时将网页(资源)转移,最终还是会使用回原来的地址,在不影响用户体验的情况下,把页面跳转到临时页面。不会记录新的 URL,下次访问还是用原 URL。
303 临时性重定向,并明确表示客户端要用 GET 方法请求资源
304 资源已找到,但客户端缓存资源未过期,仍可使用
400 请求报文中存在语法错误
401(总共返回两次) ①需要认证(弹出认证窗口)②认证失败
403 没有权限,拒绝访问
404 无法找到请求的资源(①网址错了 ②服务器拒绝请求,不说明理由)
500 服务器内部资源错误
503 服务器超载,停机维护
AJAX 是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。
AJAX = Asynchronous JavaScript and XML(异步的 JavaS3cript 和 XML)。AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
WebSocket 是一种网络通信协议,基于 TCP 实现
由于 HTTP 协议无法实现服务器主动向客户端发起消息,WebSocket 就是这样发明的。任意一方都可以通过建立的连接将数据推送到另一端。WebSocket 只需要建立一次连接,就可以一直保持连接状态。这相比于 AJAX 轮询方式的不停建立连接显然效率要大大提高。
websocket可以用于跨域,由其 API 实现,open 和 message 方法分别可以传输和接收后台的值。
https://www.cnblogs.com/LO-ME/p/10829284.html
https://gitlwz.github.io/2018/08/20/js-websocket-ky/
websocket 既然能支持跨域方法,那就是说,一个开放给公网的 websocket 服务任何人都能访问,那安全性如何保障?可以控制只有通过认证的域名才能访问吗?
将组件或页面通过服务器生成 html 字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序。
https://www.jianshu.com/p/10b6074d772c
https://xie.infoq.cn/article/5d36d123bfd1c56688e125ad3
基于 UDP 的协议:dhcp/dns/ntp 低延迟
基于 TCP 的协议:http/ftp/tls/ws/socks5 可靠性高
UDP:用户数据报协议(UDP,User Datagram Protocol)为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据报的方法,特点:
像信箱
非连接型
给定目标位置直接扔过去
无响应,不知道有没有送到
接收方会校验数据的正确性,所以送到的话一般来说是对的
支持一对一、一对多、多对一、多对多
缺点:不保证送达,数据包很小,不能保证按照发送顺序送达
优点:低延迟,丢包也不重发;如游戏,电话语音
TCP:传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,特点:
TCP 四元组:源 ip, 目的 ip,源端口,目的端口;确定网络中独一无二的连接
三次握手保证连接的可靠性(客户端请求连接,服务器同意连接,客户端表示收到服务器的消息)
只能一对一通信
TCP 半开状态:即一侧关闭了连接,不再发送数据,但可以接收数据,直至对侧也关闭了连接;另一侧没有关闭连接,仍可以发送数据。
四次挥手(客户端请求断开,服务器表示收到,服务器请求断开,客户端表示收到)
http3.0 基于 UDP 协议实现,基本继承了 HTTP2.0 的强大功能
HTTP2.0 协议的多路复用机制解决了 HTTP 层的队头阻塞问题,但是在 TCP 层仍然存在队头阻塞问题。QUIC 在一条链接上可以有多个 stream,stream 之间是互不影响的,当一个 stream 出现丢包影响范围非常小,从而解决队头阻塞问题
0RTT 连接:简单来说,基于 TCP 协议和 TLS 协议的 HTTP2.0 在真正发送数据包之前需要花费一些时间来完成握手和加密协商,完成之后才可以真正传输业务数据。但是 QUIC 则第一个数据包就可以发业务数据,从而在连接延时有很大优势,可以节约数百毫秒的时间。
连接迁移:一条连接由一个四元组标识,在当今移动互联网的时代,如果一台手机从一个 wifi 环境切换到另一个 wifi 环境,ip 发生变化,那么连接必须重新建立,inflight 的包全部丢失。QUIC 协议基于 UDP 实现摒弃了五元组的概念,使用 64 位的随机数作为连接的 ID,并使用该 ID 表示连接。基于 QUIC 协议之下,我们在日常 wifi 和 4G 切换时,或者不同基站之间切换都不会重连,从而提高业务层的体验。(五元组:SIP、SPort、DIP、DPort、协议号)
https://blog.csdn.net/wolfGuiDao/article/details/108729560
堆是一颗完全二叉树,树中每个结点的值都比其左右孩子的值大或小。
根节大于等于左右孩子就是大顶堆(从大到小),小于等于左右孩子就是小顶堆(从小到大)
空间分配:栈由操作系统自动分配释放;堆需要由程序员释放或程序结束时由 OS 回收。
结构区别:堆类似于一棵树,如堆排序;栈是一种先进后出的数据结构,类似于往箱子里放书取书,最先放进去的书在最底,拿出来时最后拿。
缓存方式:堆使用二级缓存,生命周期由虚拟机的垃圾回收算法决定;栈使用的是一级缓存,调用时处于存储空间中,调用完毕立刻释放。
栈先进后出,队列先进先出;
栈只能在表的一端插入和删除,队列只能在表的一端进行插入,并在表的另一端进行删除;
队列基于地址指针进行遍历,而且可以从头部或者尾部进行遍历,但不能同时遍历,栈只能从顶部取数据,也就是说最先进入栈底的,需要遍历整个栈才能取出来
数组静态分配内存,链表动态分配内存;
数组在内存中连续,链表不连续;
数组元素在栈区,链表元素在堆区;
数组利用下标定位,时间复杂度为 O(1),链表定位元素时间复杂度 O(n);
数组插入或删除元素的时间复杂度 O(n),链表的时间复杂度 O(1)。
https://blog.csdn.net/weixin_42438797/article/details/115339605
https://blog.csdn.net/muzidigbig/article/details/121995777
一个图包含一系列的点和一系列的边。边用来把点连接起来。路线是用来描述共用一条边的点的轨迹的术语。
树,和图一样也是一系列点的集合。有一个根节点。这个根节点有一些子节点。子节点也有它们自己的孙子节点。不断重复直到所有的数据都被用树的数据结构表示。树是没有环的图
function fibonacci(n){
if(n < 1) return 0;
if(n <= 2) return 1;
return fibonacci(n - 1) + fibonacci(n - 2)
}
function add(n, m){
var x = n + m;
if(m + 1 > 100){
return n
}else{
return add(n, m + 1)
}
}
var sum = add(1, 2)
对象继承方法:数组是一种特殊的对象,继承了 Object 的这三个方法
toString() 返回由数组中每个值的字符串形式拼接而成的一个以逗号分隔的字符串
toLocaleString() 是 toString() 方法的本地化版本,经常返回与 toString() 方法相同的值,但也不总如此
valueOf() 返回数组对象本身
转换方法:
concat() 连接两个或更多的数组,并返回结果。
copyWithin() 从数组的指定位置拷贝元素到数组的另一个指定位置中。
entries() 返回数组的可迭代对象。
every() 检测数值元素的每个元素是否都符合条件。
fill() 使用一个固定值来填充数组。
filter() 检测数值元素,并返回符合条件所有元素的数组。
find() 返回符合传入测试(函数)条件的数组元素。
findIndex() 返回符合传入测试(函数)条件的数组元素索引。
forEach() 数组每个元素都执行一次回调函数。
from() 通过给定的对象中创建一个数组。
includes() 判断一个数组是否包含一个指定的值。
indexOf() 搜索数组中的元素,并返回它所在的位置。
isArray() 判断对象是否为数组。
join() 把数组的所有元素放入一个字符串。
keys() 返回数组的可迭代对象,包含原始数组的键(key)。
lastIndexOf() 搜索数组中的元素,并返回它最后出现的位置。
map() 通过指定函数处理数组的每个元素,并返回处理后的数组。
pop() 删除数组的最后一个元素并返回删除的元素。
push() 向数组的末尾添加一个或更多元素,并返回新的长度。
reduce() 将数组元素计算为一个值(从左到右)。
reduceRight() 将数组元素计算为一个值(从右到左)。
reverse() 反转数组的元素顺序。
shift() 删除并返回数组的第一个元素。
slice() 选取数组的一部分,并返回一个新数组。
some() 检测数组元素中是否有元素符合指定条件。
sort() 对数组的元素进行排序。
splice() 从数组中添加或删除元素。
toString() 把数组转换为字符串,并返回结果。
unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
valueOf() 返回数组对象的原始值。
树的四种遍历方式时间复杂度和空间复杂度都为O(N)
栈遍历的时间复杂度是O(N)
可以用 for 循环配合 charAt 函数遍历字符串。循环从 0 开始,循环次数为 str.length ,在for循环中添加 str.charAt(i) ,charAt 中的值为循环中的次数,然后将结果输出,这样字符串就被遍历出来了。
遍历数组包括但不限于:
for 循环
for…of
for…in
forEach()
entries()
keys()
values()
reduce()
map()
工程化环境:Nodejs
包管理:npm 和 yran
构建工具:webpack 和 vite
编译等:Babel 和 TypeScript
开发辅助:ESLint
前端基建:统一前端物料(公共组件、公共UI、工具函数库、第三方 sdk)
减少资源体积:压缩代码
减少访问次数:合并代码、SSR 服务器端渲染,缓存,css 雪碧图
减少请求数量
使用更快的网络:CDN,TCP 网络链路优化(花钱),适当保持 keep-alive
unpkg:提供 npm 包的 CDN 加速,可以将一些比较固定了依赖写入 html 模版中,从而提高网页的性能
Nginx 优化缓存分配
骨架图(骗用户的)
CSS 放在 head,JS 放在 body 最下面
尽早开始执行 JS,用 DOMContentLoaded 触发
懒加载(图片懒加载、下滑加载更多、分页器)
对 DOM 查询进行缓存
减少重绘重排,使用事件委托,将频繁的 DOM 操作,合并到一起插入到 DOM 结构
节流、防抖等常用性能优化方法
script 标签加上 defer 属性 和 async 属性 用于在不阻塞页面文档解析的前提下,控制脚本的下载和执行。
defer 属性:用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。
async 属性:HTML5 新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码。
https://zhuanlan.zhihu.com/p/121056616
https://juejin.cn/post/6844903657318645767
个人认为可以说的一些点:
webpack.base.config.js 中写基础配置。如入口文件、输出文件名称、需要编译的文件扩展名、配置需要通过 loader 进行编译的文件。
webpack.dev.config.js 和 webpack.pro.config.js 分别用于添加开发环境和生产环境配置。默认 production,会做一些优化,比如压缩,但是压缩后的文件我们无法阅读;development 会优化打包速度,添加一些调试过程当中需要的辅助,比如更全面的报错信息;none 最原始的打包,不会去做任何额外的处理。
所有的配置是引入到 webpack.config.js 中的。
在 package.js 中可配置 webpack 启动命令
如果是 Vue 项目,就需要在 webpack.base.config.js 的 resolve 配置项中加入 .vue 扩展名,然后 npm 安装 Vue 相关的 loader(vue-loader、vue-style-loader、css-loader)
最后启动是用配置的命令比如 npm start
webpack 的作用:模块打包、编译兼容、能力扩展(按需加载、代码压缩等)
loader 将除 js 以外的其它资源也当成 require 的资源,如图片,CSS, json, svg,字体,通过把这些非 js 资源转化为等价的 js 文件来实现
plugin 在 webpack 则是对整体的打包结果进行处理的一种插件机制(如混淆和压缩代码、编译时配置全局变量、自动加载模块、单独抽离样式等)
babel-loader 将 ES6 转化为 ES5
file-loader 将文件输出到一个文件夹中,在代码中通过相对 URL 去引用
url-loader 与 file-loader 类似,但是能在文件很小的情况下以 base64 的方式把文件内容注入到代码中
https://juejin.cn/post/6943468761575849992
Loader:loader 就是一个打包的方案,它知道对于某个特定的文件该如何去打包。 本身 webpack 是默认只知道如何打包 js 文件的,但是不知道对于一些特定文件如何处理,loader 知道怎么处理,所以需要 loader 帮助 webpack 处理。在 module 属性里配置
npx webpack --config webpack.config.js
// --config 后面就是你自己配置的webpack文件信息
"scripts": {
"dev": "webpack --config webpack.config.js"
},
webpack 的整个打包流程:
1、读取 webpack 的配置参数;
2、启动 webpack,创建 Compiler 对象并开始解析项目;
3、从入口文件(entry)开始解析,并且找到其导入的依赖模块,递归遍历分析,形成依赖关系树;
4、对不同文件类型的依赖模块文件使用对应的 Loader 进行编译,最终转为 Javascript 文件;
5、整个过程中 webpack 会通过发布订阅模式,向外抛出一些 hooks,而 webpack 的插件即可通过监听这些关键的事件节点,执行插件任务进而达到干预输出结果的目的。
Loader 是文件转换器,将 A 文件进行编译形成 B 文件,这里操作的是文件,A.less -> A.css
plugin 是扩展器,针对的是 loader 结束后 webpack 打包的整个过程,并不直接操作文件,而是基于事件机制工作,会监听 webpack 打包过程中的某些节点,执行广泛的任务。
见CSDN收藏夹
构建 DOM -> 构建 CSSOM -> 构建渲染树 -> 布局 -> 绘制。
CSSOM 会阻塞渲染,只有当 CSSOM 构建完毕后才会进入下一个阶段构建渲染树。
通常情况下 DOM 和 CSSOM 是并行构建的,但是当浏览器遇到一个不带 defer 或 async 属性的 script 标签时,DOM 构建将暂停,如果此时又恰巧浏览器尚未完成 CSSOM 的下载和构建,由于 JavaScript 可以修改 CSSOM,所以需要等 CSSOM 构建完毕后再执行 JS,最后才重新 DOM 构建。
script 脚本的执行只在默认的情况下是同步和阻塞的。 script 标签可以有 defer 和 async 属性,这可以改变脚本的执行方式(在支持他们的浏览器)
先请求域名,代理网关判断来源,分配给最近的 CDN
github 密钥存在哪里?谁去请求密钥?
存放在当前项目的 environment。GitHub Action 请求
有简单的了解,生产服务器如果有 100 台,100 台都手动配置的话,难免会出错。docker 就解决了这种开发到线上的封装部署问题。
整体看下来,前端加密是无意义的,无非说是对于后端而言,前端直接发送明文密码,还是使用 md5、decypt、sha 等加密的密文密码,从数据层面来讲,都是『明文』,只要被劫持,就算是密文,也并不需要去破解,直接伪造请求,照样发送就好了。再加上,因为前端代码是运行在用户本地浏览器,什么加密算法都是用户可见的,混淆,散列,加密无非是增大这种可见的难度,根本上并没有解决问题。
https://juejin.cn/post/6844904074156982279
域名购买——服务器购买——服务器环境部署——上传文件到服务器
Model-View-ViewModel,主要目的是分离视图和模型,View 负责显示数据和发送命令,ViewMode 负责提供数据和执行命令,互不影响。
因为 vue 代码是通过框架编译生成原生 js 代码才可以在浏览器中运行
掩盖了操作 DOM 的细节,document.getElementById,xxx.innerHTML,文本内容的定义赋值等
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。可以将共享的数据保存到 Vuex 中,方便整个程序中的任何组件都可以获取和修改 Vuex 中保存的公共数据
每个 Vuex 应用的核心是 store,store 基本上是一个容器,包含着应用中的大部分状态。Vuex 的状态存储是响应式的,当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么响应的组件也会得到更新
改变 store 中状态的唯一途径是提交 mutation。这样可以方便的跟踪每个状态的变化
Vue.js 是采用数据劫持结合发布者-订阅者模式的方式,核心是通过 ES5(所以 vue 不支持 IE8 及以下)的 Object。defineProperty() 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
实现数据监听器 Observer,能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
实现一个指令解析器 Compile
实现一个 Watcher,作为 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图
Mvvm 入口函数,整合以上三者
<input v-model="userName" />
<input v-bind:value="userName" v-on:input="userName = $event.target.value" />
第一行的代码其实只是第二行的语法糖。input 元素本身有个 oninput 属性,这是 HTML5 新增的,类似 onchdange,每当输入框的内容发生变化,就会触发 oninput
beforeCreate 阶段:创建前,此时 data 和 methods 的数据都还没有初始化
created 阶段:创建后,data 中有值,尚未挂载,可以进行一些 Ajax 请求
beforeMount 阶段:挂载前,实例的 el 和 data 都初始化了,但是挂载之前仍为虚拟的 dom 节点
mounted 阶段:挂载后,vue 实例挂载到真实 DOM 上,可以获取 DOM 元素和访问数据
beforeUpdate 阶段:更新前,响应式数据更新时调用,发生在虚拟 DOM 打补丁之前,适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器
updated 阶段:更新后,虚拟 DOM 重新渲染和后调用,DOM 已经更新,避免在这个钩子函数中操作数据,防止死循环
beforeDestroy 阶段:实例销毁前调用,实例还可以用,this 能获取到实例,常用于销毁定时器,解绑事件
destroyed 阶段:实例销毁后调用,调用后所有事件监听器会被移除,所有的子实例都会被销毁
axios 放在哪个生命周期函数下?
在 vue 的 mounted 生命周期使用,mounted 在 html 加载完成后执行。因此进行网络数据查询,需要更新视图的只能在 mounted 生命周期里写。
父子组件嵌套时的生命周期:
先父组件 create 初始化创建,然后子组件 create。然后子组件渲染完之后,父组件再渲染
创建实例是从外到内的,渲染是从内到外的
加载渲染:
父 beforeCreate —> 父 created —> 父 beforeMount —> 子 beforeCreate —> 子 created —> 子 beforeMount —> 子 mounted —> 父 mounted
整体都一样,Vue3 只不过有了新的命名:beforeDestroy 变成了 beforeUnmounted;destroy 变成了 unmounted,名字变了但是原理没变。
Vue3 增加了 setup(),它是在 beforeCreate 和 created 之前运行的,可以用它来代替这两个钩子函数。
将同个逻辑的代码都收集在一起,单独写个 hook,然后再引入,这样就显得不乱了。之前工作中写 Vue3 就是在页面的 .vue 主文件以外把需要的逻辑都写好,比如请求写个 useRequest,下拉菜单写个 useDropDown,删改增查之类的操作写个 useAction,然后再引入到 .vue 中可以直接使用
可以通过 setup() 入口函数来调用,替代了 create 相关生命周期,调用时处于创建组件之前(组件实例并没有被创建,没有 this,通过 props 和 context 来接参,最后返回一个对象,对象中返回的属性和方法可以直接在 template 中使用
keep-alive 是 vue 内置的一个组件,可以被包含的组件保留状态,或避免重新渲染。(页面缓存)
总结来说,keep-alive 实现原理就是将对应的状态放入一个缓存对象中,对应的 DOM 节点放入缓存 DOM 中,当下次再次需要渲染时,从对象中获取状态,从缓存 DOM 中移出至挂载 DOM 节点中。
activated 和 deactivated
keep-alive 的生命周期
nextTick 是 Vue 提供的一个全局 API,由于 vue 的异步更新策略导致我们对数据的修改不会立刻体现在 DOM 变化上,此时如果想要立即获取更新后的 DOM 状态,就需要使用这个方法。
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。这种在缓冲时去除重复数据对于避免不必要的计算和 DOM 操作是非常重要的。nextTick 方法会在队列中加入一个回调函数,确保该函数在前面的 dom 操作完成后才调用。
所以当我们想在修改数据后立即看到 DOM 执行结果就需要用到 nextTick 方法。比如,我在干什么的时候就会使用 nextTick 传一个回调函数进去,在里面执行 DOM 操作即可。
简单了解 nextTick 实现:它会在 callbacks 里面加入我们传入的函数然后用 timerFunc 异步方式调用它们,首选的异步方式会是 Promise。Promise 是微任务,微任务因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕。(把传入的操作包装成异步微任务)
为什么数据更新不会立刻变化 DOM?
vue 的响应式是按一定策略进行 DOM 更新的,vue 在修改数据后,会等同一事件循环中的所有数据变化完成以后,再统一进行视图更新。同步里执行的方法,每个方法里做的事情组成一个事件循环,接下来再次调用的是另一个事件循环。
this.$set( target, key, value ) target:表示数据源,即是要操作的数组或者对象,key:要操作的的字段,value:更改的数据。
给一个对象添加属性并且让它可以响应式的进行改变。
原理:使用 Object.defineProperty 给对象的属性增加了 setter 和 getter,使 vue 能追踪属性,在他们发生修改时通知视图变更。
ref 将基本数据类型的变量转变为响应式,reactive 将复杂数据类型转变为响应式,底层使用了 Proxy。
将响应式对象转变为普通对象,但每个(某一个)属性都是 Ref 对象。
v-show、v-if/v-else/v-else-if、v-for、v-text、v-html
.lazy(减少触发次数。在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步。你可以添加 lazy 修饰符,相当于在onchange事件触发更新。)
.trim
.number
.once
computed 计算属性,依赖其它属性计算值,依赖项变化会重新执行该函数,计算属性有缓存,多次重复使用计算属性时会从缓存中获取返回值,计算属性必须有 return 关键词。
watch 监听到某一数据的变化从而触发函数。当数据为对象类型时,对象中的属性值变化时需要使用深度监听 deep 属性,也可在页面第一次加载时使用立即监听 immdiate 属性。
运用场景:
计算属性一般用在模板渲染中,某个值是依赖其它响应对象甚至是计算属性而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑。
一个父组件下不只有一个子组件。
同样,使用这份 prop 数据的也不只有一个子组件。Vue 提倡单向数据流
如果每个子组件都能修改 prop 的话,将会导致修改数据的源头不止一处,使数据的流向出现问题
所以我们需要将修改数据的源头统一为父组件,子组件想要改 prop 只能委托父组件帮它。从而保证数据修改源唯一。
组件是可复用的 vue 实例,当存在多个相同组件被引用,其实都是基于同一份对象进行构建,如果不使用 retuen 返回,则这些组件 data 都会指向同一个对象,会互相影响。使用函数就可以使变量只在当前组件生效,此时组件之间的 data 互不干扰。
当对数组进行下标有关的操作时,比如删除一个值,后面的值的 index 都会发生变化,那么 key 值自然也跟着全部发生改变。
在平时的开发过程中, 因为我们的数据绝大部分都是从后台获取来的. 数据库中每一条数据都会一个 id 作为唯一标识,而这个 id 也是我们最常使用作为 key 值的来源。
vue 列表渲染中的 key 主要用于 diff,在新旧 nodes 对比时辨识 VNodes,基于 key 的变化重新排列元素顺序。不同的 key 组件不会复用,相同的 key 组件复用
当页面的数据发生变化时,Diff 算法只会比较同一层级的节点:
如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点,不会再比较这个节点以后的子节点了。
如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。
当某一层有很多相同的节点时,也就是列表节点时,Diff 算法的更新过程默认情况下也是遵循以上原则。
涉及到 Vue 的模板编译原理,主要逻辑分三步
Mixins 是在引入组件之后,则是将组件内部的内容如 data 等方法、method 等属性与父组件相应内容进行合并。相当于在引入后,父组件的各种属性方法都被扩充了。如果在引用 mixins 的同时,在组件中重复定义相同的方法,则 mixins 中的方法会被覆盖。
是一种语法糖,与我们平常使用 $emit 实现父子组件通信没有区别,简化了父组件监听子组件并更新本地数据的写法
导航守卫就是路由跳转过程中的一些钩子函数。路由跳转是一个大的过程,这个大的过程分为跳转前中后等等细小的过程,在每一个过程中都有一函数,这个函数能让你操作一些其他的事,这就是导航守卫。类似于组件生命周期钩子函数。
https://juejin.cn/post/6844903924760051725
redux 是为了解决 react 中组件与组件之间数据传递的问题。是一个全局的数据中心,监听 state 变更并将数据传递给下层组件
组建于组件之间的传递有三种情况:
用法
引入
<script src="https://unpkg.com/[email protected]/dist/redux.js">
创造一个 store
var store = Redux.createStore((state, action)=>{
//do something
},state)
第一个参数是一个 reducer 函数,函数有 2 个参数,state 表示储存的数据,action 是一个对象,子组件里面通过 dispatch 函数来传递这个对象,这个 reducer 函数通过 action 的信息来触发对 state 的相关操作,返回一个新的 state
创建的 store 上面有两个常用的方法,dispatch 和 subscribe 方法
dispatch,传递给下层组件,下层组件利用这个方法操作 state 触发更新
subscribe,用来监听 state 变更
var unSubscribe = store.subscribe(fn)
数据变更时 fn 会运行,这个 fn 不接参数,并返回一个函数 unSubscribe
调用 unSubscribe 就会把这次的监听函数 subscribe 解绑
在 React 应用中,当某个组件的状态发生变化时,它会以该组件为根,重新渲染整个组件子树。可以通过 shouldComponentUpdate 这个生命周期进行控制 pureRender
通过 JSX 中标签加属性实现视图和数据的绑定,类组件中 render 里的大括号动态传递 state,this.state 记录数据状态,函数组件中则使用 hooks 管理状态(个人理解,不知道对不对)
Vue 实现数据绑定考的是数据劫持(Object.defineProperty()) + 发布-订阅模式。在 Vue 应用中,组件的依赖是在渲染过程中自动追踪的,所以系统能精确知晓哪个组件需要被被重新渲染。可以理解为每一个组件都已经自动获得了 shouldComponentUpdate,并且没有上面的子树问题限制
使用浏览器 localstorage 来保存应用程序的状态。我们将整个存储数据保存在 localstorage 中,每当有页面刷新或重新加载时,我们从 localstorage 加载状态。
componentWillMount:在渲染之前执行。
componentDidMount:在组件挂载后执行,可以在这里做 AJAX 请求,DOM 的操作或状态更新以及设置事件监听器。
componentWillReceiveProps:在初始化 render 的时候不会执行,它会在组件接受到新的 Props 时被触发,一般用于组件状态更新时子组件的重新渲染
shouldComponentUpdate:当 props 或 state 发生变化时。确定是否更新组件。默认情况下,它返回 true。如果确定在 state 或 props 更新后组件在重新渲染,则可以返回 false,这是一个提高性能的方法。
componentWillUpdate:在 shouldComponentUpdate 返回 true 确定要更新组件之前件之前执行。
componentDidUpdate:它主要用于更新 DOM 以响应 props 或 state 更改。
componentWillUnmount:它用于取消任何的网络请求,或删除与组件关联的所有事件监听器
React 16 之后有三个生命周期被废弃
componentWillMount
componentWillReceiveProps
componentWillUpdate
state 是组件自己管理数据,控制自己的状态,可变,必须通过 setState 更改。
props 是外部传入的数据参数,不可变,父组件通过传递 props 给子组件来更新视图,子组件不能将 prop 送回父组件。这有助于维护单向数据流,通常用于呈现动态生成的数据。
无 key 时,如果两棵树的根元素类型不同,React 会销毁旧树,创建新树。对于类型相同的 React DOM 元素,只更新不同的属性,如果类型不同,直接替换。当处理完这个 DOM 节点,React 就会递归处理子节点。比较内容,如果有不同直接替换。
有 key 时,如果 key 相同,组件有所变化,React 会只更新组件对应变化的属性;如果 key 不同,组件会销毁之前的组件,将整个组件重新渲染。
使用 React 要注意的一点是,Key 值必须是稳定的, 可预测并且是唯一的,这里给我们性能优化提供了两个非常重要的依据:
DOM 引擎、JS 引擎 相互独立,但又工作在同一线程(主线程)
函数组件是一个纯函数,接收参数并返回 React 元素,并且没有任何副作用。没有生命周期函数和 state。
通过 class xx extends React.Component 这类组件可以通过 setState() 来改变组件的状态,并且可以使用生命周期函数
定义组件时,复杂场景用类组件,简单场景用函数组件。
简单:一个组件仅仅是为了展示数据。
复杂:一个组件中有一定业务逻辑,需要操作数据,并且此时需要使用 state。
什么是纯函数?
Redux 三大原则
在 HTML 当中,像 input,textarea 和 select 这类表单元素会维持自身状态,并根据用户输入进行更新。 在 React 中,可变的状态通常保存在组件的 state 中,并且只能用 setState() 方法进行更新. React 根据初始状态渲染表单组件,接受用户后续输入,改变表单组件内部的状态。因此,将那些值由 React 控制的表单元素称为受控组件。
受控组件的特点:
setState 是同步执行的,但是 state 并不一定会同步更新,大部分情况下是异步,但在特殊环境(setTimeout、setInterval 等 DOM 原生事件)它是同步的。
this.state 是根据 isBatchingUpdate(values)的值,确定是否批处理更新。(isBatchingUpdates:是否批处理更新:true 异步,false 同步。)
React 为什么要把 setState 做成异步方式?
主要是为了减少频繁 setState 带来的性能损耗,一次真正的 setState 会 render 一次页面,如果频繁 render 这个性能损耗是巨大的。所以把 setState 设为了异步。
避免在 循环/条件判断/嵌套函数 中调用 hooks,保证调用顺序的稳定;
不能在 useEffect 中使用 useState,React 会报错提示;
类组件不会被替换或废弃,不需要强制改造类组件,两种方式能并存
当你在 useEffect 中监听对象或数组的时候,它会无条件无限执行.你可以理解为引用数据类型数据在监听时每次都生成了一个新的数据.所以必定会执行。要监听的对象修改后的值不同于修改前的就会执行,但是每次执行时监听对象都会变化,将会无限次执行。
解决办法
第二个可选参数是一个数组,是要监听的数据,当组件刷新时如果发现数组的内容和上一次一样,那么就不会运行这个 useEffect 函数,用于性能优化;要确保数组中包含了外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量,如果是空数组表示每次都是完全一样的内容,不运行。
在默认情况下,useEffect 在第一次渲染之后和每次更新之后都会执行。你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
在使用 useState 时,修改值时传入同样的值,组件不会重新渲染。
只会在当前组件的第一次渲染执行 useState 函数。
setUseState 时获取上一轮的值:我们在使用 useState 的第二个参数时,我们想要获取上一轮该 state 的值的话,只需要在 useState 返回的第二个参数,
就是我们上面的例子中的 setCount 使用时,传入一个参数,该函数的参数就是上一轮的 state 的值。
简化代码:声明一个简单的组件只要简单的几行代码;
容易上手:对于初学者来说,相对复杂的 class 的声明周期,hooks 的钩子函数更好理解;
简化业务:充分利用组件化的思想把业务拆分成多个组件,便于维护;
方便数据管理:相当于三种的提升,各个组件不用通过非常复杂的 props 多层传输,解耦操作;
便于重构:业务改变或者接手别人的代码,代码都是比较容易读懂;
获取 DOM 是 ReactDOM.findDOMNode(this.refs.box_table);
而 this.refs.box_table 直接取到的是组件,可以直接调用其内部的方法。
JSX 是 JavaScript 的语法扩展
增强 js 语义、结构清晰、抽象程度高(诞生了跨平台 React Native)、代码模块化
this.setState({
age: 2,
});
this.setState((prevState, props) => {
return {
age: prevState.age + props.age,
};
父组件向子组件通信:props
子组件向父组件通信:回调函数
跨级组件通信:context、useContext、redux
没有嵌套关系的组件通信:eventEmitter,利用全局对象来保存事件,用广播的方式去处理事件。
高阶组件和高阶函数相同。我们实现一个函数,传入一个组件,然后在函数内部再实现一个函数去扩展传入的组件,最后返回一个新的组件,这就是高阶组件的概念,作用就是为了更好的复用代码。
常用场景:权限控制、组件性能渲染追踪、页面复用
useDebounce 钩子可让你消除任何快速变化的值。当在指定的时间段内未调用 useDebounce 钩子时,去抖动的值将仅反映最新的值。 你可以轻松地确保诸如 API 调用之类的昂贵操作不会过于频繁地执行。
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
useInterval 设置了一个计时器,并且在组件 unmount 的时候清理掉了。 这是通过组件生命周期上绑定 setInterval 与 clearInterval 的组合完成的。
import React, { useState, useEffect, useRef } from 'react';
function useInterval(callback, delay) {
const savedCallback = useRef();
// Remember the latest callback.
useEffect(() => {
savedCallback.current = callback;
});
// Set up the interval.
useEffect(() => {
function tick() {
savedCallback.current();
}
if (delay !== null) {
let id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
}
uniapp 可以编译到(头条,支付宝,微信,QQ,百度)小程序,安卓版,ios 版,h5 版。通过打包实现一套代码多端运行
和 VUE 的区别:组件/标签不一样、新增了一批手机端常用的新组件 scroll-view,progress 等、uniapp自带路由和请求方式 uni.navigateTo 路由与页面跳转,uni.request 网络请求
chunk,将数组进行切分
compact,去除假值(将所有的空值,0,NaN 过滤掉)
uniq,数组去重。(将数组中的对象去重,只能去重数组不能去重对象)
merge,参数合并
尽可能低耦合,组件之间的依赖越小越好。比如不要直接修改父组件状态。
最好从父级传入所需信息,不要在公共组件中请求数据。
传入数据添加校验
通用组件往往不能完美适用所有场景,可以只实现 80% 的功能,留一个 slot,剩下的功能让父组件通过 slot 解决
类型批注和编译时类型检查:在编译时批注变量类型
类型推断:ts 中没有批注变量类型会自动推断变量的类型
类型擦除:在编译过程中批注的内容和接口会在运行时利用工具擦除
接口:ts 中用接口来定义对象类型
枚举:用于取值被限定在一定范围内的场景
Mixin:可以接受任意类型的值
泛型编程:写代码时使用一些以后才指定的类型
命名空间:标识符只在该区域内有效,其他区域可重复使用该名字而不冲突
元组:元组合并了不同类型的对象,相当于一个可以装不同类型数据的数组
https://www.cnblogs.com/webARM/p/5004595.html
在控制台输入 node 就可以进入 node 代码执行与编辑模式,原理还没了解
区别:
从登录鉴权,请求封装,路由配置,组件封装,hooks 封装,状态管理,看一遍,梳理一遍,就完事了
坚持走技术方向,加强技术深度,也希望拓宽方向,向大前端靠近。
公司前后端团队人数
新项目还是老项目?目前在使用什么技术栈(将来打算使用什么技术栈)
平时需求是以怎样的形式给前端的
入职的话主要是做什么,产品上线了吗,公司对于这个产品和产品开发团队的发展规划是什么
岗位招人的原因是什么?是已上线的项目或老项目维护人手不足,还是新项目开发人手不足?是长期项目吗?能保证至少一年稳定吗?
日常工作是什么
工作进度是怎么管理的
期望薪资
组件是否有封装规范和范例?
前端方面:各种基建是否已建成(正在或有建的打算)如公共组件、工具函数、样式库?是否有完善的文档?
产品方面:开发前是否会提供流程详图、具体字段类型和相关定义?
后端方面:是否按 Restful 规范提供接口文档?是否有必要的接口文档站点和 API 测试(如 Swagger,Apidoc)?不接受文件传输形式的接口和 Excel 形式的接口文档。不太喜欢接口标准有啥不清楚的要直接问却不写文档,个人认为能用文档解决的就不要用嘴 bb。
测试方面:为了避免测试提出一些无效的 bug,最好提前参与测试的用例评审。在实际开发中,如果有不合理功能需要修改,所有的修改都必须要求产品经理更新到 PRD 以及原型设计中。否则,测试如果不知道的话,会认为是 bug。是否写自测和 Jest 单元测试(将代码意外 bug 降到合理程度)?
https://juejin.cn/post/6844904145602740231
https://github.com/yifeikong/reverse-interview-zh