前言:
永远不要过早优化
具体项目,具体分析,脱离场景看优化没有什么意义
使用key
对于通过循环生成的列表,应给每个列表项一个稳定且唯一的key,这有利于在列表变动时,尽量少的删除,新增,改动元素,因为diff算法就是通过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方法,渲染速度更快。
使用函数式组件(无状态,无实例)
有些时候,我们一个组件只是需要接受一些prop数据,而不会改变什么东西,这个时候可以用函数式组件 ,函数式组件在渲染的时候能减少一定的脚本运行时间,内存占用也少一些
我们可以将组件标记为 functional
,它只是一个接受一些 prop 的函数,这意味它无状态 (没有响应式数据),也无实例 (没有 this
上下文)。一个函数式组件就像这样:
Vue.component('my-component', {
functional: true,
// Props 是可选的
props: {
// ...
},
// 为了弥补缺少的实例
// 提供第二个参数作为上下文
render: function (createElement, context) {
// ...
}
})
普通组件,js内存增加了30M
函数式组件,js内存增加了20M
Vue不会为函数式组件创建任何一个实例
查看VNode虚拟节点,可以看到normal组件看不到函数组件
使用计算属性
如果模板中某个数据会使用多次,并且该数据是通过计算机得到的,使用计算属性可以缓存,(缺陷不能传参)
非实时绑定的表单项(v-model.lazy)
vue设计思想是关注的是数据而不是界面,代码的可维护性和可阅读性也很重要,js执行线程和浏览器渲染线程是互斥的,所以运行动画时执行jS线程动画会卡顿
当使用 v-model 绑定一个表单项时,当用户改变表单项的状态时,也会随之改变数据,从而导致vue发生重渲染(rerender),这会带来性能的开销
特别是当改变表单项时,页面有动画在执行,由于js线程和浏览器线程是互斥的,最终会导致动画出现卡顿。
可以通过使用v-model.lazy(失去焦点时),或者不使用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、添加后,把新加的对象直接加到现有数据的最前面(缺点:数据不是实时的)
还是那个思想,我们只想要渲染新加的那个对象,其他的不渲染
在添加评论列表的时候,添加一个数据之后,一般我们会再调用一次获取数据的接口,获取完数据,拿到了json对象,然而这个对象和原来的是两个不同的数据,然后就会重新渲染,但是很多数据都是相同的,没有必要重新渲染。所以我们可以在新增加一个数据之后,直接在原来的数据中把这个新增的数据加上,不用再调用获取所有数据的借口了,问题就是如果别人也在期间加了数据,那末页面显示的可能不实时(别人新增的展示不出来),但是我们也没有规定必须要实时展示数据,因为只要一刷新就好了。
通过这个也能看出,组件细分的好处,可以避免多余的渲染。
使用v-show替代v-if
对于频繁切换显示状态的元素,使用 v-show 可以保证虚拟DOM树的稳定,避免频繁新增和删除元素,特别是对于那些内容包含大量DOM元素的节点,这一点极其重要
使用延迟装载(defer)
具体实现多种多样,有组件分批加载,数据分批加载
首页白屏时间主要受到两个因素的影响:
-
打包体积过大
巨型包需要消耗大量的传输时间,导致JS传输完成前页面只有一个
<div>
,没有可显示的内容 -
需要立即渲染的内容太多
JS传输完成后,浏览器开始执行JS构造页面
但可能一开始要渲染的组件太多,不仅JS执行的时间很长,而且执行完成后浏览器要渲染的元素过多,从而导致页面白屏
一个可行的方法是延迟装载组件,让组件按照指定的先后顺序依次一个一个渲染出来
本质是利用 requestAnimationFrame 事件分批渲染内容
react fiber
使用keep-alive
正常情况下,页面切换的时候会进行销毁,不想它被销毁的话就需要用keep-alive包裹着组件
比如列表页和详情页的切换,可以使用 keep-alive
进行缓存组件,防止同样的数据重复请求
用于缓存内部组件实例,里面有include和exclude属性,max设置最大缓存数,超过后,自动删除最久没用的。
受到keep-alive影响,其内部的组件都具有两个生命周期,activated和deactivated ,分别再组件激活和失活时触发,第一次activated是在mounted之后。
一般用在需要多个页面频繁操作的场景(导航条)
<keep-alive>
<router-view />
</keep-alive>
长列表优化
例如某乎页面数据量为1万条数据,在分批加载数据前提下怎样优化才能滑动不卡顿。
因为每次 DOM 修改,浏览器往往需要重新计算元素布局,再重新渲染。也就是所谓的重排(reflow)和重绘(repaint)。尤其是在页面包含大量元素和复杂布局的情况下,性能会受到影响。
一个常见的场景是大数据量的列表渲染。通常表现为可无限滚动的无序列表或者表格,当数据很多时,页面会出现明显的滚动卡顿
方案1:
做分页处理,首页默认展示第一页数据,滚动加载,这也是一种方案,如果列表做不了分页,那该如何处理。
方案2:
当有大量列表需要展示的时候,不需要一次性将所有列表渲染出来,只需要渲染可视区域及可视区域以下的一部分列表,并为这些已经渲染的列表设置绝对定位,然后通过计算滚动位置来不停更新开始区域的列表即可,不需要渲染所有的数据列表
虚拟列表的实现原理:只渲染可视区的 dom 节点,其余不可见的数据卷起来,只会渲染可视区域的 dom 节点,提高渲染性能及流畅性,优点是支持海量数据的渲染;当然也会有缺点:滚动效果相对略差(海量数据与滚动效果的取舍问题就看自己的需求喽
使用vue-virtual-scroller插件
cnpm install -D vue-virtual-scroller
main.js 引入这个插件:
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
import Vue from "vue";
import VueVirtualScroller from "vue-virtual-scroller";
Vue.use(VueVirtualScroller);
方案3:
滚动节流减少服务器压力。
方案4:
vue 会通过 Object.defineProperty 对数据进行劫持,来实现视图响应数据的变化,然而有些时候我们的组件就是纯粹的数据展示,不会有任何改变,我们就不需要 Vue 来劫持我们的数据,在大量数据展示的情况下,这能够很明显的减少组件初始化的时间,那如何禁止 vue 劫持我们的数据呢?可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了。
export default{
data: =>({ users: {} }),
async created {
const users = await axios.get("/api/users");
this.users = Object.freeze(users);
}
};
方案5:
添加css属性 ,这样滚动时滚动条是抖动的,添加了属性后的滚动条变长,并且拖动不流畅,但是做到了按需加载
content-visibility: auto
:如果该元素不在屏幕上,并且与用户无关,则不会渲染其后代元素。- 在一些需要被频繁切换显示、隐藏状态的元素上,使用 content-visibility: hidden,用户代理无需重头开始渲染它和它的子元素,能有效的提升切换时的渲染性能;
- content-visibility: auto 的作用更加类似于虚拟列表,使用它能极大的提升长列表、长文本页面的渲染性能;
- 合理使用 contain-intrinsic-size 预估设置了content-visibility: auto 元素的高宽,可以有效的避免滚动条在滚动过程中的抖动;
- content-visibility: auto 无法直接替代 LazyLoad,设置了 content-visibility: auto 的元素在可视区外只是未被渲染,但是其中的静态资源仍旧会在页面初始化的时候被全部加载;
- 即便存在设置了 content-visibility: auto 的未被渲染的元素,但是它并不会影响全局的搜索功能。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
p {
content-visibility: auto;
line-height: 40px;
}
.container {
overflow: auto;
}
</style>
</head>
<body>
<div class="container"></div>
<script>
let container = document.querySelector('.container');
let innerHTML = '';
for(let i = 0; i < 1000; i++) {
innerHTML += `<p>第${i}行,我是长列表</p>`
}
container.innerHTML = innerHTML;
</script>
</body>
</html>
{ content-visibility: auto; contain-intrinsic-size: 1px 5000px;}_勒布朗-前端的博客-CSDN博客
打包体积优化(webpack)
分为打包速度优化和打包体积优化