2017-07-03 74 views
0

Jake Archibald的博客JavaScript的任務調度,宏任務和Microtasks

小提琴(點擊嘿嘿):https://jsfiddle.net/1rpzycLf/

HTML:

<div class="outer"> 
    <div class="inner"></div> 
</div> 

JS:

// Let's get hold of those elements 
var outer = document.querySelector('.outer'); 
var inner = document.querySelector('.inner'); 

// Let's listen for attribute changes on the 
// outer element 
new MutationObserver(function() { 
    console.log('mutate'); 
}).observe(outer, { 
    attributes: true 
}); 

// Here's a click listener… 
function onClick() { 
    console.log('click'); 

    setTimeout(function() { 
    console.log('timeout'); 
    }, 0); 

    Promise.resolve().then(function() { 
    console.log('promise'); 
    }); 

    outer.setAttribute('data-random', Math.random()); 
} 

// …which we'll attach to both elements 
inner.addEventListener('click', onClick); 
outer.addEventListener('click', onClick); 

當運行這個作品爲inner div我得到的結果是ar e

click 
mutate 
click 
mutate 
promise 
promise 
timeout 
timeout 

我正在努力研究這是怎麼回事。 執行應該是

  1. 首先處理程序(宏任務)
  2. 過程所有 Microtasks
  3. 第二處理程序(宏任務)
  4. 過程所有 Microtasks
  5. 的setTimeout(宏任務)
  6. SetTimeout(Macrotask)

考慮到這一點,我的日誌期待輸出,而不是:

click 
promise 
mutate 
click 
promise 
mutate 
timeout 
timeout 

不知道爲什麼promises正在執行兩個點擊的事件處理程序的已被處理之後。第一個承諾應該在第一個mutate之後理想地執行,但我們可以看到顯然不是這種情況。有人知道爲什麼(使用Firefox 54.0)

回答

1

當您單擊的元素,你自然會得到click第一輸出,因爲上面有一個click事件處理程序,並在「點擊」一詞在第一事情發生的日誌在click事件處理函數中。

接下來是setTimeout(function() {}, 0);。這暫停了JavaScript的執行,就像C中的線程/進程產出。它稍後纔會執行,所以我們稍後會回到這一點。

因爲你實際上沒有做出任何承諾,它會立即解決,註銷

發生了突變第三個,因爲DOM是從上到下讀取的,並且您在承諾解析後直接突變data-random屬性。

最後,現在DOM已經完成讀取,超時完成第四個

timeout被從內<div>由於a separate execution context到了它是從所謂的記錄兩次。這可以通過以下事實看出:onclick內的console.log(this)確實是而不是提供與setTimeout(function() {console.log(this)}, 0);相同的上下文。由於bubbling與延遲setTimeout結合,它試圖從孩子<div>第一把火,然後還從<div>(你在技術上點擊)。

因此,你結束了:

click 
promise 
mutate 
timeout 
timeout 

clickpromisemutate日誌總是接踵而來,再乘以你同時點擊元素的數量。 timeout日誌將始終最後。

// Let's get hold of those elements 
 
var outer = document.querySelector('.outer'); 
 
var inner = document.querySelector('.inner'); 
 

 
// Let's listen for attribute changes on the 
 
// outer element 
 
new MutationObserver(function() { 
 
    console.log('mutate'); 
 
}).observe(outer, { 
 
    attributes: true 
 
}); 
 

 
// Here's a click listener… 
 
function onClick() { 
 
    console.clear(); // Added for clarity 
 
    console.log('click'); 
 

 
    setTimeout(function() { 
 
    console.log('timeout'); 
 
    }, 0); 
 

 
    Promise.resolve().then(function() { 
 
    console.log('promise'); 
 
    }); 
 

 
    outer.setAttribute('data-random', Math.random()); 
 
} 
 

 
// …which we'll attach to both elements 
 
inner.addEventListener('click', onClick); 
 
outer.addEventListener('click', onClick);
<div class="outer">Outer 
 
    <div class="inner">Inner</div> 
 
</div>

需要注意的是不同的瀏覽器不同的方式處理這些。我想假設鉻(我的答案是基於)正確處理,由於代碼邏輯。

火狐處理突變前的承諾:

click 
mutate 
promise 
promise 
timeout 
timeout 

邊緣的承諾之前處理兩個突變和超時:

click 
mutate 
timeout 
promise 
timeout 
promise 

IE無法處理的承諾可言,拋出一個語法錯誤。

希望這會有所幫助! :)

+0

我沒有結束你的例子。 Firefox仍然記錄'點擊' '變異' '點擊' 'mutate'首先。點擊'inner'時發生這種情況 - 編輯奇怪,當我運行你的代碼片段時,它輸出了不同的東西。 '點擊' 'mutate' 'promise' 'promise' 'timeout' 'timeout' – Aaron

+0

有趣 - 我在其他瀏覽器上也得到了不同的結果。我會更新我的答案來說明這一點。 –

+0

是的,這可能是一個問題。你怎麼知道哪一個是正確的?我也編輯了我的例子,我期待'click''promise''mutate'的執行順序 - 無論如何 – Aaron