1.虚拟DOM

什么是虚拟dom :

vdom可以看作是一个使用javascript模拟了DOM结构的树形结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
虚拟DOM是什么:
一个较轻的普通JS对象 ==> 真实DOM是一个较重的对象 => 轻重看对象内部属性多少
虚拟DOM对象有时也称为虚拟节点对象(vNode), 它包含了用于生成一个真实DOM/Node的必要信息, 比如:
<ul id="list">
<li key="1">abc1</li>
<li key="2">abc2</li>
</ul>
{
tagName: 'ul',
props: {
id: 'list',
},
children: [
{tagName: 'li', props: {}, children: ['abc1'], key: '1'},
{tagName: 'li', props: {}, children: ['abc2'], key: "2"},
]
}

DOM Diff算法
目标: 比较的结果是要确定: 哪些原来真实DOM可以复用(但内部内容可能要更新), 要创建哪些真实DOM
1. 只做同层比较 => 虚拟DOM也是一个倒立树状态结构, 只进行同层比较, 这样比较次数少,效率高
2. 确定要比较的新旧虚拟节点
没有key: 依次比较 ==> 多出的虚拟DOM, 直接创建新的真实DOM
有key: 找同名的key比较 ==> 没有找到, 直接创建新的真实DOM
3. 先比较标签名
如果不同, 直接创建新的真实DOM
如果相同, 复用原来对应的真实DOM => 如果数据内容有变化, 更新真实DOM内部内容

什么是diff算法:

diff算法就是比较新旧vdom之间的差异different,即把树形结构按照层级分解,只比较同级元素。不同层级的节点只有创建和删除操作

通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行 dom操作,从而提高性能。

2.key的作用及相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
面试题:
1). react/vue中的key的作用/内部原理
2). 为什么列表的key尽量不要用index
1. 虚拟DOM的key的作用?
1). 简单的说: key是虚拟DOM对象的标识, 在更新显示时key起着极其重要的作用
2). 详细的说: 当列表数组中的数据发生变化生成新的虚拟DOM后, React进行新旧虚拟DOM的diff比较
a. key没有变(有一个对应的)
item数据没变, 直接使用原来的真实DOM
item数据变了, 对原来的真实DOM进行数据更新
b. key变了(没有一个对应)
销毁原来的真实DOM, 根据item数据创建新的真实DOM显示(即使item数据没有变)
2. key为index的问题
1). 添加/删除/排序 => 产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低
2). 如果item界面还有输入框 => 产生错误的真实DOM更新 ==> 界面有问题
注意: 如果不存在添加/删除/排序操作, 用index没有问题
3. 解决:
使用item数据的标识数据作为key, 比如id属性值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
数据:
[
{id: 1, name: 'Tom', age: 13},
{id: 2, name: 'Jack', age: 12}
]
index作为key
vLi0 tom ==> li(tom)
vLi1 jack ==> li(jack)

[
{id: 3, name: 'bob', age: 15}
{id: 1, name: 'Tom', age: 13},
{id: 2, name: 'Jack', age: 12}
]
vLi0 bob ==> 复用 li(tom) ==> 更新dom的数据 ===> 应该要新创建的, 但复用一个错误的真实DOM
vLi1 tom ==> 复用 li(jack) ==> 更新dom的数据 ==> 应该要复用原本的, 但复用一个错误的真实DOM
vLi2 jack ==> 新建 li(jack) ==> 应该要复用原本的
id作为key
vLi1 tom ==> li(tom)
vLi2 jack ==> li(jack)

vLi3 bob ==> 新建 li(bob) ==> 新创建一个li显示
vLi1 tom ==> 复用 li(tom) ==> 直接复用li
vLi2 jack ==> 复用 li(jack) ==> 直接复用li

3.Tick及nextTick

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
什么是Tick:
取出队列中的一个回调任务到调用栈中执行就是一个tick
一个事件回调是一个Tick
一个事件回调后的所有更新DOM操作是一个Tick
每次调用nextTick都是一个新的Tick
内部用一个数组callbacks存储
包含一个所有DOM更新的watcher数组: watchers () => {[watcher, watcher]}
所有nextTick的回调
如果第一个是更新数据, callbacks的结构是:
[watchers, nextTick1, nextTick2]
如果第一个是nextTick
[nextTick1, watchers, nextTick2]
nextTick中用的是哪个异步技术呢?
优先使用微任务 => 宏任务
promise => MutationObserver => setImmediate => setTimeout

4.async和await原理

因为promise并没有真正的解决回调地狱的问题,它会将原来的任务都通过promise包装起来,使得原来的语义不那么清楚,所以诞生了async和await

async函数就是generator函数的语法糖。就是将generator函数的*换成async,将yield替换成await。同时其内置执行器,不需要再通过next来手动执行

迭代器是一种特殊对象,它具有一些专门为迭代过程设计的专有接口,生成器(generator)是一种返回迭代器的函数,通过function关键字后的星号(*)来表示,函数中会用到新的关键字yield。整个 generator 函数就是一个封装的异步任务,异步操作需要暂停的地方,都用 yield 语句注明,遇到next()时会打破停止状态继续执行

5.图片懒加载原理

​ 一张图片就是一个<img>标签,浏览器是否发起请求图片是根据<img>的src属性,所以实现懒加载的关键就是,在图片没有进入可视区域时,先不给<img>的src赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src赋值。图片的真实地址需要存储在data-src中。

​ 图片没有进入可视区域,也就是说图片的offsetTop需要小于页面的可视高度,但想一想,当图片在页面的下方的时候呢,需要页面滚动了一段距离之后才能看到图片,所以这里需要满足img.scrollTop < 页面的可视区域高度+页面滚动的高度,这里是实现图片懒加载的关键

6.从url到渲染页面的过程

  1. 得到服务器对应的IP地址
  2. 通过IP连接上服务器: 3次握手
  3. 向服务器发请求, 接收服务器返回的响应
  4. 解析响应数据(html/css/js)显示页面
    解析html => dom树
    解析css => cssom树
    解析js => 更新dom树/cssom树
    生成渲染树 = dom树 + cssom树
    布局
    渲染
  5. 断开连接:4次挥手

7.前端优化大量数据处理操作

从数据上处理:分页分表,比如前端可以把数据分页展示,后端也分段吐数据从渲染上解决:
2.1 异步渲染,比如进入页面先不渲染,然后加载好页面再渲染。
2.2 局部渲染:只渲染目前可见区域的数据,再渲染次屏数据。如虚拟列表
2.3 还有性能瓶颈,可以考虑web worker 做压缩和解码,也可以考虑离屏canvas做预渲染。

减少网络耗时:压缩数据,gzip等

数据量特别大的时候 首选 分页 然后异步渲染 局部渲染 压缩数据 canvas预渲染都可以

9.前端首屏加载性能优化

  1. 针对第三方js库 进行分离打包;第三方组件库按需引入

  2. 路由懒加载/图片懒加载

  3. 开启gzip压缩

    ​ gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度。html、js、css文件甚至json数据都可以用它压缩,可以减小60%以上的体积。前端配置gzip压缩,并且服务端使用nginx开启gzip,用来减小网络传输的流量大小。

  4. 前端代码优化:

    ​ 1. 合理使用v-if和v-show

    ​ 2. 合理使用watch和computed

    ​ 3. 使用v-for必须添加key, 最好为唯一id, 避免使用index, 且在同一个标签上,v-for不要和v-if同时使用

    ​ 4. 定时器的销毁。可以在beforeDestroy()生命周期内执行销毁事件;也可以使用$once这个事件侦听器,在定义定时器事件的位置来清除定时器。详细见vue官网

  5. webpack相关配置优化

    ​ 使用uglifyjs-webpack-plugin插件代替webpack自带UglifyJsPlugin插件来压缩JS文件

10.http 和 https 的区别及优缺点?

  • http 是超文本传输协议,信息是明文传输,HTTPS 协议要比 http 协议安全,https 是具有安全性的 ssl 加密传输协议,可防止数据在传输过程中被窃取、改变,确保数据的完整性(当然这种安全性并非绝对的,对于更深入的 Web 安全问题,此处暂且不表)。
  • http 协议的默认端口为 80,https 的默认端口为 443。
  • http 的连接很简单,是无状态的。https 握手阶段比较费时,会使页面加载时间延长 50%,增加 10%~20%的耗电。
  • https 缓存不如 http 高效,会增加数据开销。
  • Https 协议需要 ca 证书,费用较高,功能越强大的证书费用越高。
  • SSL 证书需要绑定 IP,不能再同一个 IP 上绑定多个域名,IPV4 资源支持不了这种消耗。

11. defineProperty相关

1
2
3
4
5
6
7
8
const people = { name: '阿拉德' }

Object.defineProperty(people, 'age', { value: 18 })

console.log(people.age) // 18

console.log(Object.keys(people)) // ['name']
// Object.defineProperty定义的属性不可枚举

12. 隐式转换

1
2
3
const a = void 0*;*

console.log(a == 1 && a == 2 && a == 3)*;// 使输出结果为true*

解:

1
2
3
4
5
6
var a = {
n: 1,
valueOf() {
return this.n++
}
}