我们先简单介绍下 document.Documentfragment
的作用,创建一个文档片段,这个片段存在内存中,而非真实的 DOM
节点,所以插入其子元素并不会触发页面回流/重绘,所以可以利用其连续创建多个 dom
节点
一、案例
之前的创建节点方式
let ul = document.getElementsByTagName('ul')[0]
for (let i = 0; i < 100; i++) {
let el = document.createElement('li')
el.innerHTML = i
ul.appendChild(el)
}
可以看下他的性能曲线,因为每一次的创建节点,都会触发主线程的 布局(Layout)
也就是回流操作,接着触发 重绘(Piant)
操作,也就是触发100次:
而用片段的这种方式创建,只会触发一次回流操作:
let ul = document.getElementsByTagName('ul')[0]
let frag = document.createDocumentFragment()
for (let i = 0; i < 100; i++) {
let el = document.createElement('li')
el.innerHTML = i
frag.appendChild(el)
}
ul.appendChild(frag)
性能分析:
可以对比看的出,紫色的区域 Rendering
也就是回流操作,明显用片段创建的时长更有优势
二、拓展
拓展一些关于优化渲染效率的方式:
- 1. 样式文件应当在
head
标签中,而脚本文件在body
结束前,这样可以防止阻塞的方式 - 2.
script
脚本会阻塞主渲染线程,所以渲染不需要同步执行的可以放在渲染结束后加载,使用defer
来控制脚本的加载时机 - 3. 简化并优化
CSS
选择器,尽量将嵌套层减少到最小 - 4.
DOM
的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作。 - 5. 不要一条条地改变样式,而要通过改变
class
,或者csstext
属性,一次性地改变样式 - 6. 尽量用
transform
来做形变和位移,因为此操作发生在合并线程中的画(Draw)
操作,不会影响到主渲染线程 - 7. 尽量使用离线
DOM
,而不是真实的网页DOM
,来改变元素样式。比如,操作Document Fragment
对象,完成后再把这个对象加入DOM
- 8. 先将元素设为
display: none
(需要1次回流和重绘),然后对这个节点进行100次操作,最后再恢复显示(需要1次回流和重绘)。这样一来,你就用两次重新渲染,取代了可能高达100次的重新渲染。 - 9.
position
属性为absolute
或fixed
的元素,回流的开销会比较小,因为不用考虑它对其他元素的影响。 - 10. 只在必要的时候,才将元素的
display
属性为可见,因为不可见的元素不影响回流和重绘。另外,visibility: hidden
的元素只对重绘有影响,不影响回流。 - 11. 一些
js
导致的页面回流操作可以放在window.requestAnimationFrame()
帧动画中执行
!!!备注:
** reflow
回流中,改动元素的位置/大小等信息,会触发异步统一合并更新,所以,有时候先更改width
,再获取offsetWidth/clientWidth
还是之前的,就是因为他还没有更新**