9

以下代码利用 DOM Mutation EventDOMNodeInserted来检测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 被加载时,它div在 body 中添加了一个元素来做一些测试,但它未能删除该div元素,因为该元素已被包装到包装器中,因此它不是子元素身体的元素了。

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




现在下面的代码可以实现相同的操作,并且它使用了新引入的 DOM Mutation Observers。截至目前 (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 Mutation Observers 更快,因此它能够div在该元素被包装到包装器之前删除其测试元素 ( )。




从以上两个实验中,我们发现在执行速度方面:

  • DOM 突变事件 > jQuery 的测试
  • jQuery 的测试 > DOM 变异观察者

这个结果能否恰当地证明 DOM Mutation Observers 比 DOM Mutation Events 慢?

4

2 回答 2

26

DOM Mutation Observers 并不打算比 DOM Mutation Events 更快。相反,它们旨在提高效率和安全性。

区别的基本要点是,只要有变化,DOM 突变事件就会触发。因此,例如,此代码将创建一个回调循环,最终会使浏览器崩溃。

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

事实上,它们以这种方式被调用并且经常对浏览器产生重大影响,因为它强制浏览器之间的中断重新计算样式、重排和重绘周期或更糟的是迫使浏览器重新计算样式、重排和重绘每个打回来。其他代码可能正在执行而对 DOM 进行进一步更改,这将继续被您的回调中断,这一事实进一步激怒了问题。

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

DOM Mutation Observers 解决了这些问题,顾名思义,它可以观察 DOM 的变化,并为您提供从变化开始时发生的所有变化的报告。这是一个更好的情况,因为它允许浏览器在有意义的时间通知您,例如当文档空闲并且所有其他可能进行进一步更改的 JavaScript 已完成执行时,或者在浏览器重新启动之前recalc / repaint 循环,因此它可以应用您所做的任何更改,而无需在不久之后重复该循环。

它还使您更容易管理,因为您可以扫描所有更改的元素以找到您要查找的内容,而不是为您不关心的内容编写大量案例处理代码,就像情况一样突变事件。更重要的是,它只会调用一次,因此您不必担心任何进一步的更改会影响元素,即它们不再处于更改状态,它们已经更改。

因此,在回答您的问题时,DOM Mutation Observers 速度较慢,因为它们等待 jQuery 完成对 DOM 的操作,然后才通知您 jQuery 发生了哪些变化。出于上面解释的原因和您的示例,证明它是更安全更有效的解决方案(您不再导致错误),并且您并不真正关心 jQuery 向 DOM 添加了一些内容,因为它会在不久之后将其删除。使用 Observers,您会收到一份报告,详细说明正在添加和删除的 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();
于 2012-07-30T22:26:22.967 回答
7

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

于 2012-10-09T18:52:07.867 回答