芒果团子的博客

DOM的操作成本

字数统计: 1.1k阅读时长: 4 min
2018/10/19 Share

浏览器渲染过程

  1. 解析HTML,构建DOM树(这里遇到外链,此时会发起请求)

    当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。

  2. 解析CSS,生成CSS规则树(CSSOM)

  3. 合并DOM树和CSS规则,生成render树

    DOM树从根节点开始遍历可见节点,visibility为hidden或者opacity为0的节点也是可见的,虽然页面上看不到,但是其仍占据空间。
    display为none是不可见,会在render过程中被跳过。
    保存各节点的样式及从属关系。

  4. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算

    通过布局将样式信息和属性转换为实际可视窗口的相对大小和位置。

  5. 绘制render树(paint),绘制页面像素信息。
  6. 浏览器会将各层的信息发送给GPU,GPU将各层合成(composite),显示在屏幕上

注意:

  • 在渲染过程中,1~3可能会执行多次,js操作dom、更改css样式,浏览器要重新构建DOM、CSSOM树,重新render,重新layout、paint;
  • Layout在Paint之前,因此每次重新布局(reflow 回流)后都要重新Paint渲染,这时又要去消耗GPU;
  • Paint不一定会触发Layout,比如改个颜色改个背景;(repaint 重绘)
  • 图片下载完也会重新出发Layout和Paint;

触发回流(reflow)和重绘(repaint)

回流:元素的内容、结构、位置或尺寸发生了变化,需要对样式进行重新计算和渲染
重绘:元素的背景、边框颜色、字体颜色等一些节点的变化,使用新样式重绘即可

回流的成本高于重绘,节点回流可能还会导致其子节点、兄弟节点的回流。

通过该网站可查询各个CSS属性对浏览器执行Layout、Paint、Composite的影响
csstriggers

  • 引起回流 reflow

现代浏览器对回流的优化:批处理回流问题

  1. 页面初始化
  2. DOM树变化,如增删节点
  3. Render树变化,如padding改变
  4. 浏览器窗口resize
  5. 获取元素的某些属性,offsetLeft、offsetTop、offsetWidth、offsetHeight、 scrollTop/Left/Width/Height、clientTop/Left/Width/Height、调用了getComputedStyle()或者IE的currentStyle
  • 引起重绘 repaint
  1. 由回流引起的重绘
  2. 背景色、颜色、字体改变,字体大小改变回触发回流,而不是重绘
  • 优化回流和重绘问题
  1. 修改节点样式时,尽量一次性操作
  2. 使用DocumentFragment将需要多次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
  3. 将需要多次修改的DOM元素设置display: none,操作完再显示。利用其不可见特性
  4. 避免多次读取元素的某些属性
  5. 将复杂的节点元素脱离文档流,降低回流成本

css放在头部,js放在尾部,为什么?

- css 资源阻塞

DOMContentLoaded:当初始的 HTML 文档被完全加载和解析完成之后,即DOM加载完,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完成加载。
load:load 事件触发,页面已完全加载,DOM、样式、脚本、图片等
  
生成render需要DOM和CSSOM,所以css资源会阻塞渲染,尽量对css资源进行加载,缩短渲染的时间

CSSOM未构建前会阻塞js,CSSOM构建完毕,执行js脚本

  • js 资源阻塞
  1. 外链脚本,需等待脚本下载并执行后,继续解析HTML。
  2. js线程与渲染线程互斥。
  3. 普通脚本阻塞浏览器解析,设置成async异步的话,js待下载完毕后执行,不阻塞解析,但是无法保证执行顺序,一定会在onload前,不确定在DOMContentLoaded事件的前后
  4. 脚本设置成defer延迟执行,相对于放在body最后(理论上在DOMContentLoaded事件前)

DocumentFragment 接口

表示一个没有父级文件的最小文档对象。当做一个轻量版的 Document 使用,用于存储已排好版的或尚未打理好格式的XML片段。

它不是真实DOM树中的一部分,变化时不会引起回流和导致性能问题。

使用其作为参数,append或inserted的是片段所有子节点,不是片段本身。此时所有的子节点会一次性插入到文档,造成一个重渲染

1
2
3
4
5
6
7
8
var ul = document.getElementById("ul");
var fragment = document.createDocumentFragment();
for (var i = 0; i < 20; i++) {
var li = document.createElement("li");
li.innerHTML = "index: " + i;
fragment.appendChild(li); // 担当仓库的角色
}
ul.appendChild(fragment);
CATALOG
  1. 1. 浏览器渲染过程
  2. 2. 触发回流(reflow)和重绘(repaint)
  3. 3. css放在头部,js放在尾部,为什么?
  4. 4. DocumentFragment 接口