DOM MutationObserver – 在不影响浏览器性能的情况下响应 DOM 变化。

DOM 变异事件 当时看起来是个好主意——随着 Web 开发人员创建更动态的 Web,我们自然会欢迎能够监听 DOM 中的变化并对其做出反应的能力。然而,在实践中,DOM 变异事件是一个主要的性能和稳定性问题,并且已经弃用了一年多。

然而,DOM 变异事件背后的最初想法仍然很有吸引力,因此在 2011 年 9 月,一群 Google 和 Mozilla 工程师宣布了 一项新的提案,该提案将提供类似的功能,但性能有所提高:DOM MutationObserver。这个新的 DOM API 在 Firefox 和 Webkit nightly 版本以及 Chrome 18 中可用。

最简单的 MutationObserver 实现如下所示

// select the target node
var target = document.querySelector('#some-id');

// create an observer instance
var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
        console.log(mutation.type);
    });
});

// configuration of the observer:
var config = { attributes: true, childList: true, characterData: true }

// pass in the target node, as well as the observer options
observer.observe(target, config);

// later, you can stop observing
observer.disconnect();

与已弃用的 DOM 变异事件规范相比,这个新规范的主要优势在于效率。如果您正在观察节点的变化,则只有在 DOM 完成变化后才会触发您的回调函数。当回调函数被触发时,它会提供一个 DOM 更改列表,然后您可以遍历该列表并选择做出反应。

这也意味着您编写的任何代码都需要处理观察者结果才能对您正在寻找的更改做出反应。这是一个监听可编辑有序列表中更改的观察者的简洁示例

  1. Press enter

如果您想查看此代码的运行情况,我已经将其发布在 jsbin 上

http://jsbin.com/ivamoh/53/edit

如果您使用此示例,您会注意到一些行为上的怪癖,特别是当您在每个 li 中按下 Enter 键时,回调函数会被触发,尤其是在用户操作导致节点从 DOM 中添加或移除时。这与其他技术(例如将事件绑定到按键或更常见的事件(如“click”))之间存在重要区别。MutationObservers 的工作方式与这些技术不同,因为它们是由 DOM 本身的变化触发的,而不是由通过 JS 或用户交互生成的事件触发的。

那么这些有什么用呢?

我不希望大多数 JS 开发人员现在就开始在他们的代码中添加 MutationObserver。可能这个新 API 最大的受众是编写 JS 框架的人,主要用于解决问题并创建他们以前无法完成的交互,或者至少不能以合理的性能完成。另一个用例是您使用操纵 DOM 的框架并需要高效地(并且无需 setTimeout hacks!)对这些修改做出反应的情况。

Dom 变异事件 API 的另一个常见用途是在浏览器扩展中,在接下来的一周左右,我将发布一篇后续文章,介绍 MutationObservers 在与 Firefox 附加组件中的 Web 内容交互时如何特别有用。

资源

关于 Jeff Griffiths

Jeff 是 Firefox 开发者工具的产品经理,偶尔也会担任开放 Web 黑客,工作地点在不列颠哥伦比亚省温哥华。

更多 Jeff Griffiths 的文章……


19 条评论

  1. smaug

    规范的正确链接是 http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#mutation-observers

    此外,API 很快就会取消前缀。
    我们希望摆脱变异事件,因此使用变异事件将
    导致错误控制台中出现“已弃用”警告。

    2012 年 5 月 11 日 01:41

    1. Jeff Griffiths

      啊,谢谢!我已更正了链接。

      2012 年 5 月 11 日 08:40

  2. Henri Sivonen

    “window.WebKitMutationObserver || window.MozMutationObserver”

    我很难过,我们让开发人员想要迁移到的新事物难以使用,因为在每个引擎中都使用了不同的名称。

    2012 年 5 月 11 日 02:05

    1. Masatoshi Kimura

      好消息:Firefox 将从一开始就附带未加前缀的 MutationObserver。

      2012 年 6 月 5 日 10:18

    2. codeviking

      这样做真的很难吗?

      var MutationObserver = MutationObserver || WebKitMutationObserver || MozMutationObserver;

      我不认为它很糟糕 :)

      2013 年 2 月 15 日 10:39

  3. Dao

    > 此外,API 很快就会取消前缀。

    这意味着此代码将中断

    var MutationObserver = window.WebKitMutationObserver || window.MozMutationObserver;

    您需要检查 MutationObserver 是否已定义,或者添加 || window.MutationObserver。

    2012 年 5 月 11 日 02:18

    1. Jeff Griffiths

      我已更正示例代码,以便首先查找未加前缀的版本。

      2012 年 5 月 11 日 08:41

  4. David Mulder

    就 API/设计/而言,原始 API 的设计看起来要美观得多,我真诚地不明白这个 API 是如何通过的……我的意思是,我理解原始模型的一些缺陷,但这并没有改变发生的事情,即*本质上*是事件。我的意思是,与其使用一些奇怪的新对象,不如将其制作为仅限文档的事件。新的词汇表等也可以在事件数据中实现,而无需所有这些随机的新内容。:'(

    2012 年 5 月 11 日 02:25

    1. Jeff Griffiths

      我认为反对任何类似旧 API 的东西的主要论点(除了 Smaug 正确指出的不同实现问题之外)仅仅是使用 DOM 变异事件以高性能的方式非常困难,尤其是在 Gmail 等非常“活跃”的网站上。

      2012 年 5 月 11 日 08:45

  5. smaug

    原始变异事件 API 从未被正确定义,浏览器以不同的方式实现了它的各个部分。它也很慢,并且在实现中导致了许多崩溃错误。
    新 API 旨在更快,更少地运行 js 回调函数,并在
    “稳定”、精确定义的时间运行。(目前,新 API 实现的速度是旧 API 的 5-10 倍)

    讨论过向文档添加一些 API,但有些情况下效果不佳(例如,如果您将节点从一个文档移动到另一个文档,然后再移回)

    2012 年 5 月 11 日 07:11

  6. aa

    不错!关于代码的一点评论。
    过滤器函数可以更简单一些
    filter( function(s) {return s !== ‘<br'})

    2012 年 5 月 15 日 02:46

  7. Misha Reyzlin

    嗨,我提交了一个错误,当选择框元素的选项被选中时,Mutation Observer 不会触发回调函数:https://bugzilla.mozilla.org/show_bug.cgi?id=757077

    2012 年 5 月 22 日 02:35

    1. Robert Nyman

      太好了,谢谢!

      2012 年 5 月 22 日 02:36

  8. Robert Hurst

    这是对变异事件的糟糕替代。看起来它是从另一种语言中提取的。我真的很难以置信变异事件会导致性能问题,除非实现它们的工程师做了一些偷懒的事情,例如即使没有事件绑定到该事件时也触发该事件。

    正确的解决方案应该是只有在绑定了侦听器时才触发事件。上面的 API 与 DOM 节点模型不一致,老实说,它看起来不像是属于 JavaScript 应用程序的一部分。API 很笨拙,需要很多额外的代码。

    2012 年 6 月 17 日 13:52

    1. Jeff Griffiths

      虽然 Mutation Events 可以使用而不会对性能造成太大影响,但鉴于它们的 API,创建病态案例实在太容易了。此外,事件 API 从未在所有浏览器中都得到了良好的实现,多年来,开发人员难以跨浏览器使用。请参阅此帖子,以更详细地剖析事件

      http://lists.w3.org/Archives/Public/public-webapps/2011JulSep/0779.html

      我同意 MutationObservers 作为 API 感觉不太方便,但是很难说,鉴于 DOM 如何可以通过 JS 代码进行修改,如何在不遇到我们之前在事件中遇到的问题的情况下做得更好。

      2012 年 6 月 18 日 15:26

  9. Dan

    这真的很酷。如果我错了,请纠正我,但似乎我必须指定我想观察更改的每个元素?

    我不能只监视整个 DOM 吗?我的用例是我正在创建一个脚本,该脚本将用于我个人没有开发的网站,并且我确实希望在用户界面发生任何更改时做出响应。

    2013 年 3 月 25 日 10:15

  10. muneer

    请谁能帮我一下,我们在项目中 1000 多次使用了 onPropertyChange,现在我们在 Chrome 和其他浏览器中遇到了问题,我们只使用此事件在文本框值元素的值因非用户操作(DOM 级更改)而更改时调用某些函数,那么如何解决此问题,请帮助我编写代码,因为我不太理解上面的示例
    谢谢

    2013 年 4 月 7 日 22:35

    1. Jeff Griffiths

      我最好的建议是在 Stackoverflow.com 或类似的网站上提出此问题,这些网站上有一个专门的社区,他们有兴趣回答具体的编码问题。

      2013 年 4 月 8 日 10:36

  11. ToRo

    有没有办法在更改时查看实际的属性和值?现在我只能看到 type:“attributes” 和 attributeName:“style”,但如果还能知道哪个样式发生了更改以及它的新(或旧)值是什么,那将非常有帮助。
    谢谢

    2013 年 4 月 9 日 10:01

本文的评论已关闭。