前端性能优化 24 条建议(2020)

// good
document.querySelector('ul').onclick = (event) => {
const target = event.target
if (target.nodeName === 'LI') {
console.log(target.innerHTML)
}
}
// bad
document.querySelectorAll('li').forEach((e) => {
e.onclick = function() {
console.log(this.innerHTML)
}
})
13. 留意法式的部分性
一个编写杰出的计较机法式经常具有杰出的部分性,它们偏向于援用比来援用过的数据项四周的数据项,大概比来援用过的数据项自己,这类偏向性,被称为部分性道理。有杰出部分性的法式比部分性差的法式运转得更快。
部分性凡是有两种分歧的形式:
时候部分性:在一个具有杰出时候部分性的法式中,被援用过一次的内存位置极能够在不远的未来被屡次援用。空间部分性 :在一个具有杰出空间部分性的法式中,假如一个内存位置被援用了一次,那末法式极能够在不远的未来援用四周的一个内存位置。
时候部分性示例
function sum(arry) {
let i, sum = 0
let len = arry.length
for (i = 0; i < len; i++) {
sum += arry[i]
}
return sum
}
在这个例子中,变量sum在每次循环迭代中被援用一次,是以,对于sum来说,具有杰出的时候部分性
空间部分性示例
具有杰出空间部分性的法式
// 二维数组
function sum1(arry, rows, cols) {
let i, j, sum = 0
for (i = 0; i < rows; i++) {
for (j = 0; j < cols; j++) {
sum += arry[i][j]
}
}
return sum
}
空间部分性差的法式
// 二维数组
function sum2(arry, rows, cols) {
let i, j, sum = 0
for (j = 0; j < cols; j++) {
for (i = 0; i < rows; i++) {
sum += arry[i][j]
}
}
return sum
}
看一下上面的两个空间部分性示例,像示例中从每行起头按顺序拜候数组每个元素的方式,称为具有步长为1的援用形式。 假如在数组中,每隔k个元素停止拜候,就称为步长为k的援用形式。 一般而言,随着步长的增加,空间部分性下降。
这两个例子有什么区分?区分在于第一个示例是按行扫描数组,每扫描完一行再去扫下一行;第二个示例是按列来扫描数组,扫完一行中的一个元素,顿时就去扫下一行中的同一列元素。
数组在内存中是依照行顺序来寄存的,成果就是逐行扫描数组的示例获得了步长为 1 援用形式,具有杰出的空间部分性;而另一个示例步长为 rows,空间部分性极差。
性能测试
运转情况:
cpu: i5-7400阅读器: chrome 70.0.3538.110
对一个长度为9000的二维数组(子数组长度也为9000)停止10次空间部分性测试,时候(毫秒)取均匀值,成果以下:
所用示例为上述两个空间部分性示例
从以上测试成果来看,步长为 1 的数组履行时候比步长为 9000 的数组快了一个数目级。
总结:
反复援用不异变量的法式具有杰出的时候部分性对于具有步长为 k 的援用形式的法式,步长越小,空间部分性越好;而在内存中以大步长跳来跳去的法式空间部分性会很差
参考材料:
深入了解计较机系统
14. if-else 对照 switch
当判定条件数目越来越多时,越偏向于利用 switch 而不是 if-else。
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
}
像上面的这类情况,从可读性来说,利用 switch 是比力好的(js 的 switch 语句不是基于哈希实现,而是循环判定,所以说 if-else、switch 从性能上来说是一样的)。
15. 查找表
当条件语句出格多时,利用 switch 和 if-else 不是最好的挑选,这时无妨试一下查找表。查找表可以利用数组和工具来构建。
switch (index) {
case '0':
return result0
case '1':
return result1
case '2':
return result2
case '3':
return result3
case '4':
return result4
case '5':
return result5
case '6':
return result6
case '7':
return result7
case '8':
return result8
case '9':
return result9
case '10':
return result10
case '11':
return result11
}
可以将这个 switch 语句转换为查找表
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]
16. 避免页面卡顿
60fps 与装备革新率
今朝大大都装备的屏幕革新率为 60 次/秒。是以,假如在页面中有一个动画或突变结果,大概用户正在转动页面,那末阅读器衬着动画或页面的每一帧的速度也需要跟装备屏幕的革新率连结分歧。 其中每个帧的预算时候仅比 16 毫秒多一点 (1 秒/ 60 = 16.66 毫秒)。但现实上,阅读器有整理工作要做,是以您的一切工作需要在 10 毫秒内完成。假如没法合适此预算,帧率将下降,而且内容会在屏幕上发抖。 此现象凡是称为卡顿,会对用户体验发生负面影响。
假如你用 JavaScript 点窜了 DOM,并触发款式点窜,履历重排重绘最初画到屏幕上。假如这其中肆意一项的履行时候太长,城市致使衬着这一帧的时候太长,均匀帧率就会下降。假定这一帧花了 50 ms,那末此时的帧率为 1s / 50ms = 20fps,页面看起来就像卡顿了一样。
对于一些长时候运转的 JavaScript,我们可以利用按时器停止切分,提早履行。
for (let i = 0, len = arry.length; i < len; i++) {
process(arry[i])
}
假定上面的循环结构由于 process() 复杂度太高或数组元素太多,甚至两者都有,可以尝试一下切分。
const todo = arry.concat()
setTimeout(function(){
process(todo.shift())
if (todo.length) {
setTimeout(arguments.callee, 25)
} else {
callback(arry)
}
}, 25)
倘使有爱好领会更多,可以检察一下高性能JavaScript第 6 章和高效前端:Web高效编程与优化理论第 3 章。
参考材料:
衬着性能
17. 利用 requestAnimationFrame 来实现视觉变化
从第 16 点我们可以晓得,大大都装备屏幕革新率为 60 次/秒,也就是说每一帧的均匀时候为 16.66 毫秒。在利用 JavaScript 实现动画结果的时辰,最好的情况就是每次代码都是在帧的开首起头履行。而保证 JavaScript 在帧起头时运转的唯一方式是利用 requestAnimationFrame。
/**
* If run as a requestAnimationFrame callback, this
* will be run at the start of the frame.
*/
function updateScreen(time) {
// Make visual updates here.
}
requestAnimationFrame(updateScreen);
假如采纳 setTimeout 或 setInterval 来实现动画的话,回调函数将在帧中的某个时点运转,能够恰幸亏末端,而这能够经常会使我们丧失帧,致使卡顿。
参考材料:
优化 JavaScript 履行
18. 利用 Web Workers
Web Worker 利用其他工作线程从而自力于主线程之外,它可以履利用命而不干扰用户界面。一个 worker 可以将消息发送到建立它的 JavaScript 代码, 经过将消息发送到该代码指定的事务处置法式(反之亦然)。
Web Worker 适用于那些处置纯数据,大概与阅读器 UI 无关的长时候运转剧本。
建立一个新的 worker 很简单,指定一个剧本的 URI 来履行 worker 线程(main.js):
var myWorker = new Worker('worker.js');
// 你可以经过postMessage() 方式和onmessage事务向worker发送消息。
first.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
second.onchange = function() {
myWorker.postMessage([first.value,second.value]);
console.log('Message posted to worker');
}
在 worker 中接收到消息后,我们可以写一个事务处置函数代码作为响应(worker.js):
onmessage = function(e) {
console.log('Message received from main script');
var workerResult = 'Result: ' + (e.data[0] * e.data[1]);
console.log('Posting message back to main script');
postMessage(workerResult);
}
onmessage处置函数在接收到消息后顿时履行,代码中消息自己作为事务的data属性停止利用。这里我们简单的对这2个数字作乘法处置并再次利用postMessage()方式,将成果回传给主线程。
回到主线程,我们再次利用onmessage以响应worker回传的消息:
myWorker.onmessage = function(e) {
result.textContent = e.data;
console.log('Message received from worker');
}
在这里我们获得消息事务的data,而且将它设备为result的textContent,所以用户可以间接看到运算的成果。
不外在worker内,不能间接操纵DOM节点,也不能利用window工具的默许方式和属性。但是你可以利用大量window工具之下的工具,包括WebSockets,IndexedDB以及FireFox OS公用的Data Store API等数据存储机制。
参考材料:
Web Workers
19. 利用位操纵
JavaScript 中的数字都利用 IEEE-754 标准以 64 位格式存储。可是在位操纵中,数字被转换为有标记的 32 位格式。即使需要转换,位操纵也比其他数学运算和布尔操纵快很多。
取模
由于偶数的最低位为 0,奇数为 1,所以取模运算可以用位操纵来取代。
if (value % 2) {
// 奇数
} else {
// 偶数
}
// 位操纵
if (value & 1) {
// 奇数
} else {
// 偶数
}
取整
~~10.12 // 10
~~10 // 10
~~'1.5' // 1
~~undefined // 0
~~null // 0
位掩码
const a = 1
const b = 2
const c = 4
const options = a | b | c
经过界说这些选项,可以用按位与操纵来判定 a/b/c 能否在 options 中。
// 选项 b 能否在选项中
if (b & options) {
...
}
20. 不要覆盖原生方式
不管你的 JavaScript 代码若何优化,都比不上原生方式。由于原生方式是用低级说话写的(C/C++),而且被编译成机械码,成为阅读器的一部分。当原生方式可用时,只管利用它们,出格是数学运算和 DOM 操纵。
21. 下降 CSS 挑选器的复杂性
(1). 阅读器读取挑选器,遵守的原则是从挑选器的右侧到左侧读取。
看个示例
#block .text p {
color: red;
}
查找一切 P 元素。查找成果 1 中的元素能否有类名为 text 的父元素查找成果 2 中的元素能否有 id 为 block 的父元素
(2). CSS 挑选器优先级
内联 > ID挑选器 > 类挑选器 > 标签挑选器
按照以上两个信息可以得出结论。
挑选器越短越好。只管利用高优先级的挑选器,例如 ID 和类挑选器。避免利用通配符 *。
最初要说一句,据我查找的材料所得,CSS 挑选器没有优化的需要,由于最慢和慢快的挑选器性能不同很是小。
参考材料:
CSS selector performanceOptimizing CSS: ID Selectors and Other Myths
22. 利用 flexbox 而不是较早的结构模子
在早期的 CSS 结构方式中我们能对元素实行绝对定位、相对定位或浮动定位。而现在,我们有了新的结构方式 flexbox,它比起早期的结构方式来说有个上风,那就是性能比力好。
下面的截图显现了在 1300 个框上利用浮动的结构开销:
然后我们用 flexbox 来重现这个例子:
现在,对于不异数目的元素和不异的视觉表面,结构的时候要少很多(本例中为别离 3.5 毫秒和 14 毫秒)。
不外 flexbox 兼容性还是有点题目,不是一切阅读器都支持它,所以要谨慎利用。
各阅读器兼容性:
Chrome 29+Firefox 28+Internet Explorer 11Opera 17+Safari 6.1+ (prefixed with -webkit-)Android 4.4+iOS 7.1+ (prefixed with -webkit-)
参考材料:
利用 flexbox 而不是较早的结构模子
23. 利用 transform 和 opacity 属性变动来实现动画
在 CSS 中,transforms 和 opacity 这两个属性变动不会触发重排与重绘,它们是可以由分解器(composite)零丁处置的属性。
参考材料:
利用 transform 和 opacity 属性变动来实现动画
24. 公道利用法则,避免过度优化
性能优化首要分为两类:
加载时优化运转时优化
上述 23 条倡议中,属于加载时优化的是前面 10 条倡议,属于运转时优化的是前面 13 条倡议。凡是来说,没有需要 23 条性能优化法则都用上,按照网站用户群体来做针对性的调剂是最好的,节省精神,节省时候。
在处理题目之前,得先找出题目,否则无从动手。所以在做性能优化之前,最好先观察一下网站的加载性能和运转性能。
检查加载性能
一个网站加载性能若何首要看白屏时候和首屏时候。
白屏时候:指从输入网址,到页面起头显现内容的时候。首屏时候:指从输入网址,到页面完全衬着的时候。
将以下剧本放在 前面就能获得白屏时候。
在 window.onload 事务里履行 new Date() - performance.timing.navigationStart 即可获得首屏时候。
检查运转性能
配合 chrome 的开辟者工具,我们可以检察网站在运转时的性能。
经过检查加载和运转性能,相信你对网站性能已经有了大方法会。所以这时辰要做的工作,就是利用上述 23 条倡议纵情地去优化你的网站,加油!
参考材料:
performance.timing.navigationStart
其他参考材料
性能为何相当重要高性能网站扶植指南Web性能威望指南高性能JavaScript高效前端:Web高效编程与优化理论


相关文章:
相关推荐:




