DOM突变观察器是否比DOM突变事件慢

Are DOM Mutation Observers slower than DOM Mutation Events?

本文关键字:DOM 突变 事件 观察 是否      更新时间:2023-09-26

以下代码利用DOM突变事件DOMNodeInserted来检测body元素的存在,并将其innerHTML包装到包装器中。

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>
        function DOMmanipulation() {
            if (document.body) {
                document.removeEventListener('DOMNodeInserted', DOMmanipulation);
                // DOM manipulation start
                document.body.innerHTML = '<div class="wrapper">' + document.body.innerHTML + '</div>';
                // DOM manipulation end
            }
        }
        document.addEventListener('DOMNodeInserted', DOMmanipulation);
    </script>
</head>
<body>
    <p>Lorem ipsum dolor sit amet.</p>
</body>
</html>

尽管包装成功,但仍有一个错误显示没有找到节点。这个问题的答案解释说,这是因为当jQuery被加载时,它在body中添加了一个div元素来进行一些测试,但它未能删除该div元素,因为该元素已被包装到包装器中,因此它不再是body的子元素。

上面的实验告诉我们,DOMNodeInserted事件比jQuery的测试快,因为jQuery的检测元素(div)在被jQuery移除之前就被包装了。




现在,下面的代码可以实现相同的操作,它使用了新引入的DOM突变观测器。截至目前(2012-07-11),它仅适用于Chrome 18及更高版本。

<!DOCTYPE html>
<html lang="en">
<head>
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
    <script>
        var observer = new WebKitMutationObserver(function() {  
            if (document.body) {
                observer.disconnect();
                // DOM manipulation start
                document.body.innerHTML = '<div class="wrapper">' + document.body.innerHTML + '</div>';
                // DOM manipulation end
            }
        });
        observer.observe(document, { subtree: true, childList: true });
    </script>
</head>
<body>
    <p>Lorem ipsum dolor sit amet.</p>
</body>
</html>

这些代码没有产生任何错误。这意味着jQuery比DOM突变观测器更快,因此它能够在将测试元素(div)包装到包装器中之前删除该元素。




从以上两个实验中,我们发现当涉及到执行速度时:

  • DOM突变事件>jQuery的测试
  • jQuery的测试>DOM突变观测器

这个结果能恰当地证明DOM突变观测器比DOM突变事件慢吗?

DOM突变观察器的速度并不比DOM突变事件快。相反,它们旨在提高效率和安全性。

差异的基本要点是,只要发生更改,DOM突变事件就会触发。因此,例如,这段代码将创建一个回调循环,最终会导致浏览器崩溃。

document.addEventListener('DOMNodeInserted', function() {
    var newEl = document.createElement('div');
    document.body.appendChild(newEl);
});

事实上,它们以这种方式被调用,并且经常对浏览器产生重大影响,因为它迫使浏览器之间中断重新计算样式、回流和重新绘制循环,或者更糟的是,迫使浏览器在每次回调时重新计算样式,回流和重新打印。其他代码可能正在执行,从而对DOM进行进一步的更改,这将继续被回调中断,这一事实进一步加剧了问题。

更重要的是,由于事件以与普通DOM事件相同的方式传播,您将开始听到您可能不关心或没有在代码中考虑的元素的更改。因此,DOM突变事件的整个机制很难快速管理。

DOM突变观察器通过观察DOM的更改来解决这些问题,并为您提供从更改开始发生的所有更改的报告。这是一个更好的情况,因为它允许浏览器在有意义的时间通知您,例如,当文档空闲,所有其他可以进行进一步更改的JavaScript都已执行完毕,或者在浏览器重新启动recalc/repait循环之前,这样它就可以应用您所做的任何更改,而不必在不久后重复该循环。

它还让你更容易管理,因为你可以扫描所有更改的元素来找到你想要的东西,而不是像突变事件那样为你不关心的东西编写大量的案例处理代码。更重要的是,它只会调用一次,所以你不需要担心任何进一步的更改会影响元素,即它们不再处于更改状态,它们已经更改。

因此,在回答您的问题时,DOM突变观测器的速度较慢,因为它们等待jQuery完成对DOM的操作,然后才通知您jQuery发生了什么变化。由于上面解释的原因和您的示例,这证明了它是一个更安全、更高效的解决方案(您不再会导致错误),而且您并不真正关心jQuery向DOM添加了一些内容,因为它会在不久后将其删除。使用Observer,您会收到一份报告,详细说明正在添加和删除的jQuery元素。

然而,这仍然有点麻烦,因为您必须通过将元素与发生的所有更改进行匹配来弄清楚实际发生了什么。事实是,就您而言,没有发生任何事情(添加和删除了相同的元素),因此DOM的结构实际上没有任何变化。为了帮助实现这一点,有一个名为MutationSummary的小库:

http://code.google.com/p/mutation-summary/

它计算更改的净效果,并且只调用传入这些更改的回调。因此,在您的情况下,根本不会调用回调,因为更改的净效果为零。

例如,对于以下内容,您将只得到一次更改。正文样式已更改为左侧:1000px。尽管我以1000为增量进行了更改。变化的净影响只是其初始值和最终值之间的差异。

function moveBody() {
    for (var i = 0; i < 1000; i++) document.body.style.left = i + 'px';
}
moveBody();

简单的答案是突变观测器是异步的。它们会在发动机感觉良好时发送,在发生更改后的一段时间内发送,在某些情况下,在发生大量更改后发送。这可能是在DOM突变事件侦听器通知您很久之后,但与此同时,引擎可以自由完成工作,而不必为每一个微不足道的DOM更改不断创建和冒泡事件。