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到渲染页面的过程
- 得到服务器对应的IP地址
- 通过IP连接上服务器: 3次握手
- 向服务器发请求, 接收服务器返回的响应
- 解析响应数据(html/css/js)显示页面
解析html => dom树
解析css => cssom树
解析js => 更新dom树/cssom树
生成渲染树 = dom树 + cssom树
布局
渲染
- 断开连接:4次挥手
7.前端优化大量数据处理操作
从数据上处理:分页分表,比如前端可以把数据分页展示,后端也分段吐数据从渲染上解决:
2.1 异步渲染,比如进入页面先不渲染,然后加载好页面再渲染。
2.2 局部渲染:只渲染目前可见区域的数据,再渲染次屏数据。如虚拟列表
2.3 还有性能瓶颈,可以考虑web worker 做压缩和解码,也可以考虑离屏canvas做预渲染。
减少网络耗时:压缩数据,gzip等
数据量特别大的时候 首选 分页 然后异步渲染 局部渲染 压缩数据 canvas预渲染都可以
9.前端首屏加载性能优化
针对第三方js库 进行分离打包;第三方组件库按需引入
路由懒加载/图片懒加载
开启gzip压缩
gizp压缩是一种http请求优化方式,通过减少文件体积来提高加载速度。html、js、css文件甚至json数据都可以用它压缩,可以减小60%以上的体积。前端配置gzip压缩,并且服务端使用nginx开启gzip,用来减小网络传输的流量大小。
前端代码优化:
1. 合理使用v-if和v-show
2. 合理使用watch和computed
3. 使用v-for必须添加key, 最好为唯一id, 避免使用index, 且在同一个标签上,v-for不要和v-if同时使用
4. 定时器的销毁。可以在beforeDestroy()生命周期内执行销毁事件;也可以使用$once这个事件侦听器,在定义定时器事件的位置来清除定时器。详细见vue官网
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)
console.log(Object.keys(people))
|
12. 隐式转换
1 2 3
| const a = void 0*;*
console.log(a == 1 && a == 2 && a == 3)*;
|
解:
1 2 3 4 5 6
| var a = { n: 1, valueOf() { return this.n++ } }
|