ECMAScript与DOM是两个不同的"小岛"在浏览器中, 每次互相的访问,都要收取"过路费", 因此我们要尽量减少过路费, 减少DOM与ECMAScript的交互
DOM访问和修改
1. 合并多次临时修改, 再最终一次统一修改dom
function innerHTMLLoop2(){
let content = '';
for(let i =0; i< 15000; i++){
content += 'a';
}
document.getElementById('temp').innerHTML = content;
}
2. 使用 innerHTML 还是 DOM
大多数情况下 浏览器已经对
innerHTML和DOM已经没有太大的效率区别 ,但是如果, 在一个对性能悠着苛刻要求的操作, 并更新一大段HTML, 推荐使用innerHTML方式
3. 节点克隆
elment.cloneNote() 替代 document.createElement() 在绝大多数的浏览器中,都比克隆节点有效率
4. HTML 集合
HTML集合一直与文档保持这连接, 每次我们访问这个HTML集合时, 都会重复执行查询过程, 哪怕只是获取集合里的元素个数(即访问集合的length属性)
优化:
- 访问集合元素时候, 尽量使用局部变量缓存起来 (例如: 把集合的长度缓存到一个局部变量)
- 先将集合元素拷贝到数组中, 那么访问它的属性会更快, 但是拷贝到数组会带来额外的销毁, 会多遍历一次集合 , 因此需要特定条件下评估
5. 遍历 DOM
children和childNodes中,children比childNodes要快, 因为 HTML源码中的空白(文本节点)并不在children中, 但是会出现在childNodes中querySelectorAll()这种query开头的api 要比document.getElement这种api要快, 尤其是,当我们需要 处理大量的 组合查询, 而且querySelector我们得到的是静态列表, 而document.gelElement得到的是HTML集合
6.重绘与重排
- DOM树:
DOM树中的每一个需要显示的节点, 在渲染树中至少存在一个对应的节点(隐藏DOM除外)
- 渲染树:
渲染树中的节点被称为"帧(frame)或者盒(box)"
- 总结:
-
当DOM上的变化引起了元素的几何物理,添加或删除DOM, 位置改变, 内容改变(被另一个不同尺寸的图片替换), 浏览器窗口发生改变,页面渲染器初始化, 那么会发生重排 + 重绘
-
根据改变的范围和程度, 渲染树会有对应部分的重新计算(重排), 但是也有整个页面的重排, 例如: 出现滚动条
-
如果知识改变颜色等,那么只有重绘没有重排
-
尽量避免使用:
- offsetTop, offsetLeft, offsetWidth, offsetHeight
- scrollTop, scrollLeft, scrollWidth, scrollHeight
- clientTop, clientLeft, clientWidth, clientHeight
- getComputedStyle() 因为浏览器通常会通过队列化修改和批量执行重排, 来优化浏览器对重排性能的优化. 但是, 这些属性都会强制刷新重排队列, 并要求立刻执行以保证获取最新的值. 因此我们应该在修改样式的工程中, 最好避免使用上面列出的属性. 因为他们都会刷新渲染队列, 即使你是在获取最近未发生改变的或者最新改变无关的布局信息.
-
最小化重绘和重排 利用
cssText来代替el.style.borderLeft=1px, 例如:el.style.cssText ="border-left:1px; border-right:2px; padding:5px" -
批量修改DOM来减少重绘和重排
思路: 元素脱离文档流 ==> 对其应用多重改变 ==> 把元素带回文档中
- (隐藏元素, 应用修改, 重新显示). 例如:
```jsvar ul = document.getElementById('mylist'); ul.style.display = 'none'; ul.appendChild(tempNode); ul.style.display = 'block'; ```2. 使用文档片段 document.fragment
```js var fragment= document.createDocumentFragment(); fragment.appendChild(tempNode); fragment.appendChild(temp2); var ul = document.getElementById('mylist'); ul.appendChild(fragment); ```3. 把原始元素拷贝到一个脱离文档的节点中, 对副本进行操作, 完成后替换原始元素
js var old = document.getElementById('mylist'); var clone = old.cloneNode(true); clone.appendChild(temp); old.parentNode.replaceChild(clone, old); -
缓存布局信息
// bad
myElement.style.left = 1 + myElement.offsetLeft + 'px'; // 每次都去读与写, 读取的时候回有强制刷新
myElement.style.top = 1 + myElement.offsetTop + 'px';
if(myElement.offsetLeft >=500){
stopAnimation();
}
// good
var current = myElement.offsetLeft; // 只读一次
current++;
myElement.style.left = current + 'px'; // 只有写,没有读, 不在查询, 因此效率大大提升
myElement.style.top = current + 'px';
if(current>=500){
stopAnimation();
}
- 动画过程中, 让目标元素脱离文档流, 减小重排范围
- 利用绝对定位
7. 较少DOM上的事件绑定
大量的DOM上的事件绑定会严重损耗我们的DOM性能 经典案例: ul上绑定事件监听, 利用冒泡机制, 从而监听我们的
li, 而不是在100个甚至更多的li上 逐一绑定监听事件
总结
访问和操作DOM是现代浏览器的重要部分
- 最小化DOM访问次数, 尽可能在js端处理
- 如果要多次访问DOM节点, 请用局部变量储存它
- 使用速度更快的API, 例如
querySelectorAll和firstElementChild- 尽量一次性批量修改样式, 和 "离线"操作DOM树, 使用缓存, 并减少访问布局信息的次数
- 动画中使用绝对定位
- 使用事件委托减少事件处理器的数量