2014-02-28 55 views
2

我正在構建一個undomanager,類似於在各種瀏覽器中尚未準備好的W3C undomanager。我實現了一個簡單的事務處理調用,它在調用DOM時調用回調函數,然後將必要的結構添加到數組中,稍後可用於撤消(或重做)更改。如果在修改DOM後立即調用斷開連接,則不會調用MutationObserver回調

一個簡單的例子:

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更改實際完成之前,因此沒有檢測到任何東西。

這發生在兩個25火狐和Chrome 32

我想了一秒鐘,由於observer是一個局部變量,也許它超出範圍太快,但將其更改爲一個全局變量沒有幫幫我。我不得不推遲電話disconnect()給DOM一個看起來似乎趕上的機會。

這是一個瀏覽器的bug嗎?一旦DOM準備好了,有沒有更好的方法可以調用disconnect()

回答

2

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)); 

} 
+0

通過當前的堆棧,你的意思是調用堆棧? – izak

+2

是的,瀏覽器中的任何*異步*只能在立即調用堆棧爲空之後運行。這裏關於異步函數的線程數量很多 – megawac

+0

@izak抱歉 - 我沒有解決問題 – megawac

相關問題