前端性能优化——DOM

性能优化系列笔记,提升用户体验

导致大多数网站和应用程序出现性能滞缓的一个主要因素就是通过javascript低效访问HTML页面元素。因为所有浏览器的javascript引擎独立于其渲染引擎,操作了DOM对象后,会触发一些浏览器行为,比如布局(layout)和绘制(paint),这些行为是非常耗时的。

浏览器工作大致流程

以Webkit为例:
webkit

  • 解析HTML/SVG/XHTML,并生成一棵DOM tree,解析CSS会产生CSS规则树,解析Javascript脚本,主要是通过DOM API和CSSOM API来操作DOM Tree和CSS Rule Tree.
  • 解析完成后,浏览器引擎会通过DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree
  • 对Render tree的各个节点计算布局信息,比如box的位置与尺寸
  • 根据Render tree并利用浏览器的UI层进行绘制

注意:

  1. Rendering Tree 渲染树并不等同于DOM树,因为一些像Head或display:none的东西就没必要放在渲染树中了(但是 visibility 属性值为“hidden”的元素仍会显示)。
  2. CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element。也就是DOM结点。然后,计算每个Element的位置,这又叫layout和reflow过程
    more

渲染

paint
上图流程中有很多连接线,这表示了Javascript动态修改了DOM属性或是CSS属会导致重新Layout,有些改变不会.

  • Repaint:屏幕的一部分要重画,比如某个CSS的背景色变了。但是元素的几何尺寸没有变
  • Reflow:意味着元件的几何尺寸变了,我们需要重新验证并计算Render Tree。Render Tree的一部分或全部发生了变化。在HTML第一次被加载的时候,会有一次layout之外,js脚本的执行和样式的改变同样会导致浏览器执行layout

Reflow的成本比Repaint的成本高得多的多。DOM Tree里的每个结点都会有reflow方法,一个结点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。

一般情况下,浏览器的layout是lazy的,也就是说:在js脚本执行时,是不会去更新DOM的,任何对DOM的修改都会被暂存在一个队列中,在当前js的执行上下文完成执行后,会根据这个队列中的修改,进行一次layout。

然而有时希望在js代码中立刻获取最新的DOM节点信息,浏览器就不得不提前执行layout,这是导致DOM性能问题的主因。

reflow:

  • 通过js获取需要计算的DOM属性
  • 添加或删除DOM元素
  • resize浏览器窗口大小
  • 改变字体
  • css伪类的激活,比如:hover
  • 通过js修改DOM元素样式且该样式涉及到尺寸的改变

如果脚本中要获取以下属性,那么积累的reflow将会马上执行,已得到准确的位置信息

  1. offsetTop, offsetLeft, offsetWidth, offsetHeight
  2. scrollTop/Left/Width/Height
  3. clientTop/Left/Width/Height
  4. getComputedStyle(), 或 currentStyle

针对DOM操作的优化,有很多方式和技巧。例如,对于一些DOM的修改操作,可以使用documentFragment、cloneNode等方式,这种方法的原理在于

对一个不在Render tree上的节点进行一系列操作,再把这个节点添加回Render tree,这样无论多么复杂的DOM操作,最终都只会触发一次layout。

多使用innerHTML

当使用innerHTML设置为某个值时,后台会创建一个HTML解释器,然后使用内部的DOM调用来创建DOM结构,而非基于JAVASCRIPT的DOM调用。由于内部方法是编译好的而非解释执行,故执行的更快。对于小的DOM更改,两者效率差不多,但对于大的DOM更改,innerHTML要比标准的DOM方法创建同样的DOM结构快得多

requestAnimationFrame
window.requestAnimationFrame()这个方法是用来在页面重绘之前,通知浏览器调用一个指定的函数,以满足开发者操作动画的需求。这个方法接受一个函数为参,该函数会在重绘前调用。
使用requestAnimationFrame浏览器可以优化并行的动画动作,更合理的重新排列动作序列,并把能够合并的动作放在一个渲染周期内完成,从而呈现出更流畅的动画效果

参考:
https://leozdgao.me/why-dom-slow/
http://coolshell.cn/articles/9666.html
https://github.com/kahn1990/web_performance_optimization