如果在修改DOM后立即调用disconnect,则不调用MutationObserver回调

时间:2014-02-28 13:20:43

标签: javascript mutation-observers

我正在构建一个非漫游者,类似于在各种浏览器中尚未准备好的W3C undomanager。我实现了一个简单的transact调用,在调用DOM的同时调用回调,然后将必要的结构添加到一个数组中,以后可以用来撤消(或重做)更改。

一个简单的例子:

function transact(callback){
    /* Watch content area for mutations */
    observer = new MutationObserver(function(){
        /* TODO: collect mutations in here */
        alert('Mutations observed');
    });
    observer.observe(document.getElementById('content'), {
      attributes: false,
      childList: true,
      characterData: false,
      subtree: false
    });

    /* Perform the callback */
    callback();

    /* Stop observing */
    //observer.disconnect();
    setTimeout(function(){ observer.disconnect();}, 1);

}

要使用它:

transact(function(){
    var p = document.createElement('p');
    p.innerHTML = 'Hello';
    document.getElementById('content').appendChild(p);
});

如果我立即调用observer.disconnect(),变异观察者永远不会到达alert调用,但如果我使用setTimeout,它可以正常工作。

我非常乐意接受setTimeout调用,唯一的问题似乎是对于更大的更改,你必须将断开连接延迟800毫秒。

几乎就像在DOM更改实际完成之前发生了断开连接,因此没有检测到任何内容。

这在Firefox 25和Chrome 32中都会发生。

我想了一下,因为observer是一个局部变量,也许它过早地超出了范围,但将它改为全局变量并没有帮助。我必须推迟调用disconnect(),让DOM有机会赶上它。

这是一个浏览器错误吗?一旦DOM再次准备就有更好的方法来调用disconnect()吗?

1 个答案:

答案 0 :(得分:2)

MutationObservers are async by specfication,因为在调用回调函数之前,它们会等待当前堆栈为空。这很有用,因此每次更改DOM时都不会调用回调,但只有在完成所有更改后才会调用。 See how are MutationObserver callbacks fired?

如果您查看规范链接,您会注意到MutationEvent之前涉及的步骤:

  • MutationObserver会收到变异通知
  • 将突变追加到自上次事件/ takeRecords
  • 以来的当前突变集
  • 在当前堆栈为空后调用回调函数(这就是为什么您的代码按预期设置超时工作的原因 - setTimeout将在超时和堆栈清空后调用该函数)
  • 清空记录队列并继续观察

更新抱歉,为了解决您的实际问题,我认为这可能与alert回调中的MutationObserver有关。对于要处理的突变,它绝对不应该花费超过几毫秒的时间,它应该在setTimeout之前发生。无论如何,一个绝对有效的解决方案是在MutationObserver回调中添加一个队列处理器,而不是使用超时。

function transact(callback){
    var queue = [], listener; //queue of callbacks to process whenever a MO event occurs
    /* Watch content area for mutations */
    var observer = new MutationObserver(function(){ //made observer local
        /* TODO: collect mutations in here */
        alert('Mutations observed');
        while(listener = queue.shift()) listener();
    });
    observer.observe(document.getElementById('content'), {
      attributes: false,
      childList: true,
      characterData: false,
      subtree: false
    });

    /* Perform the callback */
    callback();

    /* Stop observing */
    //observer.disconnect();
    queue.push(observer.disconnect.bind(observer));

}