在富客户端时代,通过JavaScript来改变元素结构是必须的。这是通过DOM或document对象模型来完成操作,而对于当大量操作或者你想写一个比别人更快的应用,那么了解一些对DOM的原理变得尤为重要。

当元素被真正插入到document时会引发浏览器reflow(即重绘元素样式并计算,这是浏览器的事我们不讨论)。比如我们插入一个新元素、改变元素的CSS样式、调整浏览器窗口、访问offsetHeight等等都会引发若干次 reflow。

说了好几次 reflow,那么提高应用效率,减少浏览器触发 reflow 的次数是一个正比关系。

常见几种容易引发 reflow

reflow-chart

从图上看,可以看得出关于一些会引发 reflow 的CSS属性。

一、CSS样式操作

我们应该尽可能的减少对 element.style.* 的操作次数,因为每一行脚本代码都将可能引发 reflow(为什么说可能,因为对于像现代浏览器会对这种操作做适当的优化)。

function setLinkStyle() {
    element.style.fontWeight = 'bold';
    element.style.color = '#000';
    // 更多类似
}

更适合的方式应该是

.link { font-weight: bold; color: #000; }
function setLinkStyle() {
    element.className = 'link';
}

二、尽可能处理好DOM后再插入

这种创建或修改一个也会引发一个 reflow。假如我们有个函数用来创建新元素,链接文本为 Text;并提供 classlink

function addLink(parentElement) {
    var element = document.createElement('a');
    parentElement.appendChild(element);
    element.innerHTML = 'Text';
    element.className = 'link';
}

这将会引发三次 reflow。因此,我们更快的写法应该是:

function addLink(parentElement) {
    var element = document.createElement('a');
    element.innerHTML = 'Text';
    element.className = 'link';
    parentElement.appendChild(element);
}

三、DocumentFragment 方法

DocumentFragment 他是一个轻量级且不需要父代的DOM对象,他对浏览器或手机浏览器的兼容支持非常完美。

比如我们连续创建30个链接,比如:

function addBatchLink(parentElement) {
    for (var i = 0; i < 30; i++) {
        var element = document.createElement('a');
        element.innerHTML = 'Text';
        element.className = 'link';
        parentElement.appendChild(element);
    }
}

这里会引发30次 reflow。而通过 DocumentFragment 的方式只会触发一次 reflow

function addBatchLink(parentElement) {
    var fragment = document.createDocumentFragment();
    for (var i = 0; i < 30; i++) {
        var element = document.createElement('a');
        element.innerHTML = 'Text';
        element.className = 'link';
        fragment.appendChild(element);
    }
    parentElement.appendChild(fragment);
}

四、offsetHeight 等属性

appendChild 一个元素后接着再访问元素的 offsetHeight 样式属性值时也会立刻触发一个 reflow,且脚本会等待 reflow 完成后才会继续,虽然计算会非常快。

jQuery的DOM操作

jQuery对DOM操作的核心文件是 manipulation.js,这里扩展了一些像 appendafterbefore 等关于DOM操作接口。而这里的核心是 domManip() 方法,他会使用 createDocumentFragment 创建一个全新元素对象,最后才执行一次 appendChild 插入元素。

可见jQuery已经做了很多优化。

重绘与reflow

浏览器会引起重绘,可能会是当添加元素、改变元素可见性、改变背景颜色等时。

而reflow是当DOM被改变风格(我们大可指CSS)时会执行,当改变className、浏览器窗口大小等会进行若干次 reflow,当父元素执行 reflow时,也一并会考虑到子元素的变化,甚至前后代元素,最后变成所有都需要 reflow

重绘与reflow,他们就像是相互关联着,都是影响 DOM 操作效率最主要原因。

影响 reflow 时间

  • DOM结构深度越深,修改元素后代越多所需要时间越长。
  • 最小化CSS规则、删除未使用的CSS,尽可能使用className。

总结

关于 reflow 的话已经说了那么多,如果我们抛开UI阻塞、重绘等问题外,对于SPA应用而言操作DOM的效率影响者整个应用的效率。了解为什么 reflow 是一个至关重要问题,这样才能写出更高效的前端代码。

参考资料