JavaScript性能 - DOM重排 - 谷歌文章

6

有人能向我证明下面给出的建议(以下是复制的链接:http://code.google.com/speed/articles/javascript-dom.html)是更快的,即在修改DOM元素之前删除它们,然后重新插入它们。

我希望看到一些数字来证明这一点。他们研究这个问题很好,但我认为如果不包括具体的“问题”以及解决方案如何在速度方面进行修复(作为文章标题Speeding up JavaScript),那么本文非常薄弱。

该文章...

流外DOM操作

这种模式使我们可以创建多个元素并将它们插入到DOM中触发单个回流。它使用一个称为DocumentFragment的东西。我们在DOM之外创建一个DocumentFragment(因此它不在流中)。然后,我们创建并添加多个元素到其中。最后,我们将DocumentFragment中的所有元素移动到DOM中,但只触发一次回流。 问题

让我们编写一个函数,为元素内的所有锚点更改className属性。我们可以通过简单地迭代每个锚点并更新其href属性来实现这一点。问题是,这可能会导致每个锚点都重新回流。

function updateAllAnchors(element, anchorClass) {
  var anchors = element.getElementsByTagName('a');
  for (var i = 0, length = anchors.length; i < length; i ++) {
    anchors[i].className = anchorClass;
  }
}

解决方案

为了解决这个问题,我们可以从DOM中删除元素,更新所有锚点,然后将元素插入回原来的位置。为了帮助实现这一点,我们可以编写一个可重用的函数,它不仅可以从DOM中删除一个元素,还可以返回一个函数,以便将该元素插入回其原始位置。

/**
 * Remove an element and provide a function that inserts it into its original position
 * @param element {Element} The element to be temporarily removed
 * @return {Function} A function that inserts the element into its original position
 **/
function removeToInsertLater(element) {
  var parentNode = element.parentNode;
  var nextSibling = element.nextSibling;
  parentNode.removeChild(element);
  return function() {
    if (nextSibling) {
      parentNode.insertBefore(element, nextSibling);
    } else {
      parentNode.appendChild(element);
    }
  };
}

现在我们可以使用这个函数来更新脱离文档流的元素内部的锚点,只有在移除该元素和插入该元素时才触发重排。
function updateAllAnchors(element, anchorClass) {
  var insertFunction = removeToInsertLater(element);
  var anchors = element.getElementsByTagName('a');
  for (var i = 0, length = anchors.length; i < length; i ++) {
    anchors[i].className = anchorClass;
  }
  insertFunction();
}
5个回答

5

在JavaScript性能分析中,要获取有意义的数字并不容易,因为你真正想要做的是减少重绘和回流,这些操作大多数分析工具都无法显示。你可以使用Firebug paint events extension,可视化地显示你节省了多少次重绘。


不错的扩展,但是对我来说,这两个示例似乎都进行了相同数量的重绘。您有没有可能在pastebin.me上提供演示? - redsquare
我必须承认,这个扩展会使事情变得缓慢,因此在计算密集型操作(例如Google地图缩放)上很难看到差异。 - Alex

2
我在页面上放了一些链接,并测试了本文中的方法与仍在页面上的元素设置类名的方法进行比较。我在Firefox 3、IE 8和Chrome 3中尝试了这个方法。
我为具有不同颜色和不同字体大小的链接创建了类。由于不同类别的链接文本大小不同,因此我确信页面确实需要重新排版。
对于任何合理数量的链接(多达几千个),删除和添加元素的速度略慢。
对于极大数量的链接(10,000个),删除和添加元素的速度略快。
然而,差异非常小。您必须有几千个链接才能注意到任何差异,在10,000个链接处仍然只有大约20%的差异。
因此,我的发现是,您不能期望从此方法获得任何显着变化。如果您存在性能问题,则可能会有其他方法可以获得更好的结果。例如,我将尝试更改父元素的类名而不是所有子元素,并让CSS完成工作。我之前做过的测试表明,这可能快十倍左右。

2

这与使用documentFragments来初始化元素而不是dom更或多或少相同。文档片段最终会更快,因为它们具有更少的结构和实际渲染需要考虑。

以下是John Resig关于文档片段性能优势的一些注释(jquery当前正在使用):

http://ejohn.org/blog/dom-documentfragments/


1
简短的回答是,对实际页面DOM的更改会触发Javascript事件、CSS评估,影响传播的变化会影响其周围DOM的解释等。不连接到它们的流动节点没有任何类似的连接,对它们的操作要便宜得多。
一个类比:如果你是《玩具总动员4》的动画师,在近乎最终渲染中看到需要在场景的织物物理上进行更改,你会在进行完整详细的重新渲染来检查场景时进行更改,还是在进行这些更改时关闭纹理和颜色并降低分辨率?

我想要数字。好处在哪里?理论很棒。就像伊拉克有大规模杀伤性武器一样,看看发生了什么。我仍然不明白它如何使js更快。我已经对其进行了分析,但没有看到任何好处。只是想知道我错过了什么。 - redsquare
@redsquare:那篇文章的标题有误导性,而且在某些方面也不准确。它并不一定会让JS更快...它只是让浏览器更快地呈现DOM。换句话说,JS可能运行得一样快(甚至更慢),但页面会出现得更快,因为浏览器需要花费更少的时间进行回流和重绘。回流和重绘是浏览器渲染HTML和CSS的方式。JS性能与浏览器渲染性能不同。例如,即使您的网站没有JS,表格的渲染成本也很高。 - Pauan
@redsquare: 我觉得他们之所以称之为“加速JavaScript”,是因为JS通常是触发回流和重绘的元凶。换句话说,通过改变你的JS代码,可以让页面看起来更快。但它不是因为JS更快而显得更快,而是因为浏览器进行的回流和重绘更少。例如,一个操作DOM的Java小程序可能也会有同样糟糕的表现,所以称之为“JavaScript速度”是不准确的。真正应该说的是渲染速度。 - Pauan
@redsquare:顺便说一句,我相当确定对JS代码进行分析不会有太大的差别,因为你不应该测量JS代码。你应该测量浏览器呈现所需的时间,而这更难以分析。 - Pauan

0

除了查看页面外,没有可靠的方法来判断回流何时完成。

添加或逐个更改元素所花费的时间计算几乎相同。脚本不会在元素之间等待浏览器,浏览器会追赶上来。

有时候你想立即看到某些东西,即使渲染整个内容需要更长时间-而有时候你想再等一会儿,等到所有东西都准备好后再显示。

如果你无法确定哪种方式更好,请让设计师查看逐个和全部版本-但不要询问两个设计师!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接