前言:
永远不要过早优化,见招拆招
使用key
对于通过循环生成的列表,应给每个列表项一个稳定且唯一的key,这有利于在列表变动时,尽量少的删除,新增,改动元素
index作为key值是唯一的,但不够稳定,比如插入某元素,会导致后面的所有元素重新渲染,而我们想要的是只渲染新加的元素
比如有一个列表,我们需要在中间插入一个元素,在不使用 key 或者使用 index 作为 key 会发生什么变化呢?先看个图
如图的 li1 和 li2 不会重新渲染,这个没有争议的。而 li3、li4、li5 都会重新渲染
因为在不使用 key 或者列表的 index 作为 key 的时候,每个元素对应的位置关系都是 index,上图中的结果直接导致我们插入的元素到后面的全部元素,对应的位置关系都发生了变更,所以在 patch 过程中会将它们全都执行更新操作,再重新渲染。
这可不是我们想要的,我们希望的是渲染添加的那一个元素,其他四个元素不做任何变更,也就不要重新渲染
而在使用唯一 key 的情况下,每个元素对应的位置关系就是 key,来看一下使用唯一 key 值的情况下
这样如图中的 li3 和 li4 就不会重新渲染,因为元素内容没发生改变,对应的位置关系也没有发生改变。
这也是为什么 v-for 必须要写 key,而且不建议开发中使用数组的 index 作为 key 的原因
对于展示数据使用冻结对象Object.freeze()
冻结的对象不会被响应化
当观察的对象是嵌套对象的时候,就需要递归
如果对象很多,嵌套结构很深,遍历的时间就会变长
思考:
所有的属性都需要响应式吗?
在原始数据中,有些不需要去响应式的,叫做展示数据,比如商品列表,商品价格图片这些都不能操作的
数据响应式:数据变化后,会自动重新运行依赖该数据的函数(重要)
-
被监控的函数
render、computed回调、watch、watchEffect
-
函数运行期间用到了响应式数据(响应式数据一定是个对象)
-
响应式数据变化会导致函数重新运行
Vue会判断对象是否被冻结Object.isFrozen(),如果为true就不遍历
let obj = {
a: 1,
b: 3
}
/* 冻结对象 */
Object.freeze(obj);
obj.c = 2;
console.log(obj); // {a: 1, b: 3}
/* 判断对象是否被冻结 */
console.log(Object.isFrozen(obj)); // true
判断对象是否被冻结Object.isFrozen()
Object.isFrozen(obj);
冻结前:
冻结后:
相比于冻结前,每个属性都少了get和set方法,渲染速度更快。
使用函数式组件(无状态,无实例)
渲染函数 & JSX — Vue.js
之前创建的锚点标题组件是比较简单,没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些 prop 的函数。在这样的场景下,我们可以将组件标记为 functional
,这意味它无状态 (没有响应式数据),也无实例 (没有 this
上下文)。一个函数式组件就像这样:
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
普通组件,js内存增加了30M
函数式组件,js内存增加了20M
Vue不会为函数式组件创建任何一个实例
查看VNode虚拟节点,可以看到normal组件看不到函数组件
使用计算属性
如果模板中某个数据会使用多次,并且该数据是通过计算机得到的,使用计算属性可以缓存
非实时绑定的表单项(v-model)
当使用 v-model 绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致 vue 发生重渲染(rerender),这会带来一些性能的开销。
特别是当用户改变表单项时,页面有一些动画正在进行中,由于JS执行线程和浏览器渲染线程是互斥的,最终会导致动画出现卡顿。
我们可以通过 v-model.lazy 或不使用 v-model 的方式解决该问题,但是注意,这样可能会导致在某一个时间段内数据和表单项是不一致的。
v-model 监听 @input 事件
v-model.lazy 监听 @change 事件
保持对象引用稳定 (组件要不要重新渲染)
在绝大多数情况下, vue 触发 rerender 的时机是其依赖的数据发生变化
若数据没有发生变化,哪怕给数据重新赋值了, vue 也是不会做出任何处理的
function hasChanged (x, y) {
if (x === y) {
return x === 0 && 1 / x !== 1 / y;
} else {
return x === x || y === y;
}
}
NaN === NaN false
NaN !== NaN true
+0 === -0 true
1/+0 Infinity
1/-0 -Infinity
如果需要,只要能保证组件的依赖数据不发生变化,组件就不会重新渲染
对于原始数据类型,保持其值不变即可
对于对象类型,保持其引用不变即可
从另一方面来说,由于可以通过保持属性引用稳定来避免子组件的重新渲染,那么我们应该细分组件来尽量避免多余的渲染
比如,做用户评论,提交最新的一条评论后,更新评论列表,有两种方法
1、添加后,重新全部从服务器拿(缺点:rerender触发频繁)
2、添加后,把新加的对象直接加到现有数据的最前面(缺点:数据不是实时的)
还是那个思想,我们只想要渲染新加的那个对象,其他的不渲染
使用v-show替代v-if
对于频繁切换显示状态的元素,使用 v-show 可以保证虚拟DOM树的稳定,避免频繁新增和删除元素,特别是对于那些内容包含大量DOM元素的节点,这一点极其重要
使用延迟装载(defer)
首页白屏时间主要受到两个因素的影响:
-
打包体积过大
巨型包需要消耗大量的传输时间,导致JS传输完成前页面只有一个
<div>
,没有可显示的内容 -
需要立即渲染的内容太多
JS传输完成后,浏览器开始执行JS构造页面
但可能一开始要渲染的组件太多,不仅JS执行的时间很长,而且执行完成后浏览器要渲染的元素过多,从而导致页面白屏
一个可行的方法是延迟装载组件,让组件按照指定的先后顺序依次一个一个渲染出来
本质是利用 requestAnimationFrame 事件分批渲染内容
react fiber