背景
为什么要做前端的性能优化?网站的性能对于用户的留存率、转化率有很大的影响,且非常直接的说提高网站性能就是在提高收入
规则
1、减少重绘重排
浏览器渲染过程:
- 解析HTML生成DOM树
- 解析CSS生成CSSOM规则树
- 将DOM树与CSSOM规则树合并在一起生成渲染树
- 遍历渲染树开始布局,计算每个节点的位置大小信息
- 将渲染树每个节点绘制到屏幕
重排:当改变DOM元素位置或者大小时,会导致浏览器重新生成渲染树,这个过程就是重排
重绘:当重新生成渲染树后,就要将渲染树每个节点绘制到屏幕,这个过程就叫重绘
重排会导致重绘,但是重绘不会导致重排,比如改变字体颜色只会导致重绘
导致重排的操作有:
-
添加或删除可见的DOM元素
-
元素位置改变
-
元素尺寸改变
-
内容改变
-
浏览器窗口尺寸改变
如何减少重排重绘?
- 用JS修改样式时,最好不用直接写样式,而是替换class来改变样式
- 如果要对DOM元素执行一系列操作,可以将DOM元素脱离文档流,修改完成后,再将它带回文档。(比如使用隐藏元素display:none或者文档碎片DocumentFragement)
-隐藏元素可以通过v-show,因为v-show的实质就是设置css中的display为none
-文档碎片DocumentFragement是什么
场景:比如要在一个ul中添加100个li,如果不使用文档碎片,就需要使用append100次追加,会导致浏览器一直不停地渲染,非常消耗资源,
但是使用文档碎片,就可以先将100个li添加到文档碎片中,然后直接把文档碎片追加到ul。所以文档碎片其实就是一个临时仓库。
//先创建文档碎片
let oFragmeng = document.createDocumentFragment();
let oUl = document.createElement("ul");
for(var i=0;i<10000;i++) {
var op = document.createElement("li");
var oText = document.createTextNode(i);
op.appendChild(oText);
//先附加在文档碎片中
oFragmeng.appendChild(op);
}
//最后一次性添加到document中
oUl.appendChild(oFragmeng);
2、使用事件委托
事件委托利用到了事件冒泡,使用事件委托可以节省内存
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>凤梨</li>
</ul>
// 使用了事件委托,在父元素中添加点击事件,点击子元素li时就会发生冒泡,传到父元素上 document.querySelector('ul').onclick = (event) => {
const target = event.target
if (target.nodeName === 'li') {
console.log(target.innerHTML)
}
}
// 没有使用事件委托,将会占用大量内存,影响网页的性能 document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
3、if-else对比switch
当判断条件数量越来越多时,越倾向于使用switch
如果要判断color的值,通过if-else语句则要判断多次,直到结果为true
通过switch判断的话只需要进行一次判断,并且可读性更好
if (color == 'blue') {
} else if (color == 'yellow') {
} else if (color == 'white') {
} else if (color == 'black') {
} else if (color == 'green') {
} else if (color == 'orange') {
} else if (color == 'pink') {
}
switch (color) {
case 'blue':
break
case 'yellow':
break
case 'white':
break
case 'black':
break
case 'green':
break
case 'orange':
break
case 'pink':
break
}
4、查找表
当条件语句特别多时,不妨可以试一下查找表,查找表可以使用数组和对象来进行构建
根据数字来查找,获得对应结果
// 数组
const results = [result0,result1,result2,result3,result4,result5,result6,result7,result8,result9,result10,result11]
return results[index]
根据字符串来查找,获得对应结果
const map = {
red: result0,
green: result1,
}
return map[color]
5、使用 Web Workers (不是每个浏览器都支持这个特性的)
Web Worker使用其他工作线程从而独立于主线程之外,它可以执行任务而不干扰用户界面。
使用 Web Worker 也非常简单,只需要预先在 Worker 中注册 message 事件,在主线程中 postMessage 给 Worker 处理就好了。处理完后可以再通过 postMessage 传结果给主线程。
适合于处理纯数据,或者是与浏览器UI无关的长时间运行脚本
在worker内不能直接操作DOM节点,也不能使用window对象的默认方法和属性
// main.js 创建一个新的worker,Worker 不能读取本地文件,所以这个脚本必须来自网络
let myWorker = new Worker('work.js');
first.onchange = function() {
// 主线程调用postMessage,向myWorker发送消息
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
// 使用onmessage以接收worker回传的信息
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
}
// 完成任务后,主线程可以将其关掉
worker.terminate();
// work.js 可以写一个事件处理函数代码作为响应
onmessage = function(e) {
console.log('Message received from main script');
// 消息本身作为事件的data属性进行使用
let workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
// 对传过来的参数进行处理后可再次使用postMessage方式,将结果回传给主线程
postMessage(workerResult);
}
6、使用transform和opacity属性更改来实现动画
在CSS中,transform和opacity这两个属性的更改不会触发重排和重绘
7、使用requestAnimationFrame来实现视觉变化
屏幕刷新频率即图像在屏幕上更新的速度,这个频率大概是60Hz,动画本质就是要让人眼看到图像被刷新而引起变化的视觉效果,这个变化要以连贯的、平滑的方式进行过渡。
requestAnimationFrame是浏览器用于定时循环操作的一个接口,主要用途是按帧对网页进行重绘。设置这个API的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。(由于requestAnimationFrame目前还存在兼容性问题,而且不同的浏览器还需要带不同的前缀。)
代码中使用这个API,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。
function updateScreen(time) {
// 视觉更新变化的操作
}
// updateScreen为传入的回调函数,即动画函数。首先要判断document.hidden属性是否为true,页面处于可见状态下才会执行
requestAnimationFrame(updateScreen);
而setTimeout是通过设置一个间隔时间来不断的改变图像位置,从而达到动画效果的,但其执行的时间并不确定,就会导致它执行的步调和屏幕刷新步调不一致,出现丢帧现象。
setTimeout的执行只是在内存中对图像属性进行改变,这个变化必须要等到屏幕下次刷新时才会被更新到屏幕上。如果两者的步调不一致,就可能会导致中间某一帧的操作被跨越过去,而直接更新下一帧的图像