MutationObserver回调函数何时触发?

49

我知道MutationObservers回调可能会在DOM更改后的某个时刻被调用。但问题是:这些回调的时间是什么时候?

这些回调是:

  • 在DOM变异发生后立即被调用,
  • 在操纵DOM的函数完成后立即被调用,
  • 在调用栈为空时立即被调用,
  • 在DOM变异发生后立即进入队列,
  • 在操作DOM的函数完成后立即进入队列,或者
  • 在其他时间被调用?

例如,如果执行以下代码(其中此处定义了setZeroTimeout):

var target = document.body;

new MutationObserver(function(mutations) {
  console.log('MutationObserver');
}).observe(target, {
  attributes: true,
  childList: true,
  characterData: true
});


// Post message
setZeroTimeout(function () { console.log('message event'); });
// DOM mutation
target.setAttribute("data-test", "value");

在"message event"之前或之后打印"MutationObserver",这是固定的顺序吗?还是由浏览器实现来决定?

在Chromium 26中,我发现"MutationObserver"在"message event"之前被打印出来了,尽管DOM修改是在消息发布之后的。也许这表明MutationObserver回调没有使用事件队列。

我尝试搜索HTML规范、DOM规范或浏览器实现文档,但没有找到与此行为相关的任何内容。

请问有关MutationObservers回调时间的说明或文档吗?


1
我的猜测是,如果变异观察器使用事件队列,那么“消息事件”在“MutationObserver”之前出现是完全不足为奇的。在“setZeroTimeout”中,“postMessage”处理程序会触发一个事件(添加到事件队列中),然后“setAttribute”调用会触发观察器(这可能也会在消息事件之后将一个事件添加到队列中)。 - apsillers
1
@apsillers,我先收到的是“MutationObserver”,而不是“message event”。 - FelisCatus
啊,我的错误。那么,是的,这似乎表明MutationObserver不使用事件队列。 - apsillers
2
这可能会更加复杂,因为 postMessage 事件的行为可能与其他事件不同,但我对此并不完全确定。 - apsillers
1
@joseeight 但是这个文档并没有定义它们何时被“调用”。相反,它说:“HTML标准定义了这个概念如何与平台的其他部分集成以及何时调用。” - FelisCatus
显示剩余2条评论
2个回答

24

MutationObservers被异步但是“即将”触发,这意味着它们会在队列中的其他事物(如布局、绘制或触发事件等)之前触发。

这有助于改善同步性的损失,因为您无需担心屏幕闪烁或其他不良影响在您的观察者有机会做出反应之前发生。

在开发人员笔记中,他们谈到了一个“微任务结束”的时间模型。我认为这篇文章缺乏文档化。


感谢您的详细解释!我认为在基于回调顺序的假设上撰写任何内容之前,我应该等待模型被修复和记录。 - FelisCatus

23

根据 WHATWG更新的DOM规范,两年后我将回答自己的问题。

如规范所示:

要排队一个变异观察器复合微任务,请执行以下步骤:

  1. 如果设置了变异观察器复合微任务排队标志,请终止这些步骤。
  2. 设置变异观察器复合微任务排队标志。
  3. 排队一个复合微任务以通知变异观察器。

而“排队一个复合微任务”链接到 HTML规范中的一个部分,解释了微任务队列模型。

因此,我们可以得出结论:MutationObserver回调作为微任务被触发,确实比@Scott Miles上面的答案建议的任务队列任务更快。

为了更好地理解事件循环和处理模型,可以查看HTML规范的事件循环部分

个人而言,我很高兴看到MutationObserver已成为标准,并且具有良好记录和一致的时间模型。由于MutationObserver 在大多数现代浏览器中都得到支持,我认为现在它们非常适合生产使用。


感谢您的评论,@FelisCatus;我正在使用MutationObserver来检测Angular SPA应用程序中的更改,问题是这些回调在每次用户导航时都会被触发多次。我想知道是否有一种方法可以检测到何时完成此操作,也就是说,当DOM最终加载/更改并且除非用户再次导航,否则不会再发生更改。 - David
@david 你不能确定不会再有更多的变化发生,因为这些观察者可能会继续触发,并且在$timeout等方面可能会有更多的变化。然而,我认为只要没有挂起的 AJAX 请求并且 DOM 没有更新了1或2秒钟,那么可以非常安全地假设 DOM 将不再发生变化。 - FelisCatus

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