2

我正在构建一个 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 有机会赶上它。

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

4

1 回答 1

3

MutationObservers 按规范是异步的因为它们将等待当前堆栈为空,然后再调用您的回调函数。这很有用,因此不会在每次对 DOM 进行更改时调用回调,而是仅在进行所有更改后调用。看看 MutationObserver 回调是如何触发的?

如果您查看规范链接,您会注意到 a 之前涉及的步骤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));

}
于 2014-02-28T13:24:26.970 回答