JavaScript的一大特色就是單線程,而這個線程中擁有惟一的一個事件循環。
// setTimeout中的回調函數纔是進入任務隊列的任務 setTimeout(function() { console.log('xxxx'); }) // 很是多的同窗對於setTimeout的理解存在誤差。因此大概說一下誤解: // setTimeout做爲一個任務分發器,這個函數會當即執行,而它所要分發的任務,也就是它的第一個參數,纔是延遲執行
事件循環的順序,決定了JavaScript代碼的執行順序。它從script(總體代碼)開始第一次循環。以後全局上下文進入函數調用棧。直到調用棧清空(只剩全局),而後執行全部的micro-task。當全部可執行的micro-task執行完畢以後,本輪循環結束。下一輪循環再次從macro-task開始,找到其中一個任務隊列執行完畢,而後再執行全部的micro-task,這樣一直循環下去。html
當咱們在執行setTimeout任務中遇到setTimeout時,它仍然會將對應的任務分發到setTimeout隊列中去,可是該任務就得等到下一輪事件循環執行。html5
<div id="div"> begin </div>
setTimeout(function() { // 應該是這裏執行前開始渲染ui,試試用alert阻塞下。 alert(' ui 已經渲染完畢了嗎? '); console.log('timeout1'); }) new Promise(function(resolve) { console.log('promise1'); for(var i = 0; i < 1000; i++) { i == 99 && resolve(); } console.log('promise2'); }).then(function() { console.log('then1'); alert(' ui 開始渲染 '); }) console.log('global1'); div.innerHTML = 'end';
上述代碼中修改了div的內容,那麼在執行那句js代碼以後渲染引擎開始修改div的內容呢?chrome
根據HTML Standard,一輪事件循環執行結束以後,下輪事件循環執行以前開始進行UI render。即:macro-task任務執行完畢,接着執行完全部的micro-task任務後,此時本輪循環結束,開始執行UI render。UI render完畢以後接着下一輪循環。segmentfault
在chrome瀏覽器中執行以上代碼,控制檯先輸出promise1,promise2,global1,then1(micro-task任務輸出),彈出'ui 開始渲染'警告框,點擊肯定以後,頁面中的'begin'變爲'end',再彈出警告框'ui 已經渲染完畢了嗎?' ,點擊確認以後再輸入timeout1.promise
<div class="outer" style="width:200px;height:200px;background-color: #ccc"> 1 <div class="inner" style="width:100px;height:100px;background-color: #ddd">begin</div> </div>
// Let's get hold of those elements var outer = document.querySelector('.outer'); var inner = document.querySelector('.inner'); var i = 0; // 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() { i++; if(i === 1) { inner.innerHTML = 'end'; } console.log('click'); setTimeout(function() { alert('錨點'); 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 時程序依次的執行順序是:瀏覽器
此時,因爲用戶點擊事件onclick產生的macrotask執行完畢,JS stack 清空,開始執行microtask.dom
此時,microtask 執行完畢,JS stack 清空,可是因爲事件冒泡,接着執行outer上的onclick事件.函數
此時,因爲outer上的onclick事件產生的macrotask執行完畢,JS stack 清空,開始執行microtask.ui
此時,本輪事件循環結束,UI 開始 render.線程
此時,UI render 完畢,開始下一輪事件循環.
到此爲止,整個事件執行完畢,咱們能夠看到在彈出警告框以前inner的內容已經改變。
inner.addEventListener('click', onClick); outer.addEventListener('click', onClick); inner.click();
此時的執行順序是:
此時,inner 的 onclick 已經出 JS stack,可是script(總體代碼)尚未出 JS stack,還不能執行microtask,因爲冒泡,接着執行 outer 的 onclick.
接着執行的outer.setAttribute('data-random', Math.random());,可是因爲上一個mutation microtask還處於等待狀態,不能再添加mutation microtask,因此這裏不會將 mutate 壓入到 microtask。接着執行:
此時,inner.click()執行完畢,script(總體代碼)已出 JS stack,JS stack 清空,開始執行mircotask.
此時,全部的mircotask執行完畢,本輪事件循環結束,UI 開始 render.
此時,UI render 完畢,開始下一輪事件循環.
到此爲止,整個事件執行完畢,咱們能夠看到在彈出警告框以前inner的內容已經改變。
總結:首先執行macrotask,當js stack爲空時執行microtask,接着開始UI render,接着再開始下一輪循環