【问题标题】:MutationObserver callback not called if disconnect called immediately after modifying the DOM如果在修改 DOM 后立即调用断开连接,则不会调用 MutationObserver 回调
【发布时间】:2014-04-01 12:35:42
【问题描述】:

我正在构建一个 undomanager,类似于 W3C 的 undomanager,它还没有在各种浏览器中准备好。我实现了一个简单的事务调用,它在监视 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()

【问题讨论】:

    标签: javascript mutation-observers


    【解决方案1】:

    MutationObservers are async by specfication,因为它们将等待当前堆栈为空,然后再调用您的回调函数。这很有用,因此不会在每次对 DOM 进行更改时调用回调,而是仅在进行所有更改后调用。 See how are MutationObserver callbacks fired?

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

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

    更新抱歉,为了解决您的实际问题,我认为这可能与 MutationObserver 回调中的 alert 有关。处理突变绝对不应该超过几毫秒,并且绝对应该发生在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));
    
    }
    

    【讨论】:

    • 当前栈是指调用栈?
    • 是的,浏览器中的任何 async 都只能在立即调用堆栈为空后运行。这里有大量关于异步函数的线程
    • @izak 抱歉 - 我没有解决问题
    • 没问题。感谢您的队列建议,我几乎可以肯定这会成功,似乎不可能不这样做:-) 很快就会在这里报告。
    • Fwiw,我知道回调将被称为异步,但我不知道这意味着只有当立即调用堆栈为空时。上面有一个小问题,但由于异步性质,我认为没有办法真正克服它。如果您对 DOM 进行了多次更改,请将第一个更改包含在一个 transact() 调用中,而不是其余部分,然后一切都将在事务中结束。换句话说,这对于记录脚本的结尾非常有用。对于我的应用程序来说,这可能就足够了。它肯定不比目前的 setTimeout 解决方案差。
    猜你喜欢
    • 1970-01-01
    • 2018-04-14
    • 1970-01-01
    • 2013-03-08
    • 1970-01-01
    • 2015-11-28
    • 2014-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多