《高性能JavaScript》读书笔记
加载和执行
- 所有的
script
放在</body>
之前 - 合并脚本,尽量减少
script
,因为每个script
就是一次http请求 - 内嵌的
javascript
不能放在加载CSS的link
之后 - 无阻塞脚本,使用
script
的defer
属性 - 动态创建
script
元素来下载并执行代码,动态创建的建议放在head里
数据存取
- 能用局部变量就用局部变量,因为查找作用域链越深越消耗性能
- 若是多处使用某个全局属性,应该把全局属性赋值给一个局部
- 对象的属性比字面量消耗性能,再多处使用某对象的属性,应该将其赋值给局部变量,若是方法则不需要,因为可能会改变内部方法的this指向
- 尽量不要用闭包,闭包应及时释放
- 不要使用with语句,
- 嵌套的对象成员会影响性能,尽量少用
- 访问字面量和局部变量的速度最快,访问数组元素和对象成员相对较慢
DOM编程
- 减少DOM操作
- 计算在js完成,之后再修改DOM
性能差的
function innerHTMLLoop(){
for(let count = 0; count < 15000; count++){
document.getElementById('here').innerHTML += 'a';
}
}
性能较好的
function innerHTMLLoop(){
let content = '';
for(let count = 0; count < 15000; count++){
content += 'a';
}
document.getElementById('here').innerHTML += content;
}
-
大多数情况下,节点克隆效率比创建元素高
-
HTML集合是类数组,拥有length和Index,但没有push
-
HTML集合是实时更新的,所以在遍历时,添加元素后,长度会自动发生变化
这是一个死循环
let alldivs = document.getElementsByTagName('div');
for(let i = 0; i < alldivs.length; i++){
document.body.appendChild(document.createElement('div'));
}
-
HTML集合的性能是昂贵的,可以将length属性缓存,或将集合转化为真数组,不同场合考虑不同用法
-
需要多次访问DOM属性或方法时,将其赋值给局部变量
-
在IE中,查找DOM节点时,使用
nextSibling()
-
使用
children
会比childNodes
效率高 -
选择器API使用
querySelectorAll
或querySelector
,比document.getElementById()
这些更有效率,而且返回的是NodeList,这是一个类数组,而且不会实时对应文档结构 -
较少重绘和重排
重排和重绘
重排是浏览器重新渲染,重绘只绘制一部分DOM元素
触发重排的条件
- 添加或删除可见DOM
- 元素位置改变
- 元素尺寸改变(包括:外边距、内边距、边框厚度、高度、宽度等属性改变)
- 内容改变,例如文本改变或图片被另一个不同尺寸的图片改变
- 页面渲染器初始化
- 浏览器窗口尺寸改变
触发重绘的条件
例如,背景色发生变化,颜色发生变化
在元素脱离文档流时,改变元素大小不会触发重排
- 较少重复修改CSS样式
性能差的
const el = document.getElementById('mydiv');
el.style.borderLeft = '1px';
el.style.borderRight = '2px';
el.style.padding = '5px';
//发生了三次重排
性能较好的
const el = document.getElementById('mydiv');
el.style.cssText = 'border-left: 1px; border-right: 2px; padding: 5px;';
//只会修改DOM一次
使用cssText
会覆盖已存在的样式信息,如果不想覆盖可以这样做
el.style.cssText += '; border-left: 1px;';
批量修改DOM
当需要对DOM元素进行一系列操作时,可以利用以下方法,减少重绘和重排
- 隐藏元素,应用修改,重新显示
- 使用文档片段在当前DOM之外构建一个子数,再把它拷贝到文档
- 将原始元素拷贝到一个脱离文档的节点中,修改副本,完成后替换原始元素
第一种方法
给元素设置display: none
,修改元素的一系列操作,元素显示display: block
第二种方法
const fragment = document.createDocumentFragment();
appendDataToElement(fragment, data); // 一系列元素操作
document.getElementById('mylist').appendChild(fragment)
第三种
const old = document.getElementById('mylist');
const clone = old.cloneNode(true);
appendDataToElemnet(clone, data);
old.parentNode.replaceChild(clone, old);
推荐使用第二种,因为发生的重排最少
- 较少使用会使队列刷新的方法
offsetTop | offsetLeft | offsetWidth | offsetHeight
scrollTop | scrollLeft | scrollWidth | scrollHeight
clientTop | clientLeft | clientWidth | clientHeight
getComputedStyle() (computedStyle in IE)
- 需要动画的DOM,使其脱离文档流
使用绝对位置定位页面的动画元素,使其脱离文档流
元素执行动画
执行完毕,恢复位置
-
在很长的列表或表格时,避免使用
:hover
-
能使用事件委托的就使用事件委托
算法和流程控制
- 较少的条件分支,使用if,较多时使用switch
- 在遍历时,知道长度的使用for循环,不要使用
for...in
,不知道长度时才使用for...in
- 循环之前,把元素长度赋值给局部变量,避免每次循环查找长度
- 倒循环的性能比正循环要好
- 函数迭代(
forEach
)性能比for循环差
快速响应的用户界面
用于执行js和更新用户界面的线程,称为浏览器UI线程,该线程要么执行js代码,要么执行UI更新,因此当js执行过长时,会影响UI的更新,比如点击按钮,Js执行过长时,无法看到按钮被按下的样式
- js执行时间不要超过100毫秒,否则用户会觉得与界面失联了
- 定时器的时间最少是25毫秒,再少由于各个浏览器的实现机制的问题,会引发其他不可预测的问题
- 当不需要操作DOM的时候,而且是复杂的计算时,如编码/解码大字符串,复杂数学运算(包括图像或视频处理),大数组排序等,使用
Worker
。任何操作100毫秒的处理过程,都应该考虑Worker
其他
创建数组或对象时,使用字面量
const o = {
name: 'zhangsan'
}
const arr = [1, 2]
运算时使用位运算,会使性能提高很多
使用原生方法,原生方法是效率最高的方法