摘要:的一大特點就是單線程,而這個線程中擁有唯一的一個事件循環。事件循環基本概念代碼的執行過程中,除了依靠函數調用棧來搞定函數的執行順序外,還依靠任務隊列來搞定另外一些代碼的執行。之后全局上下文進入函數調用棧。
JavaScript的一大特點就是單線程,而這個線程中擁有唯一的一個事件循環。事件循環基本概念
JavaScript代碼的執行過程中,除了依靠函數調用棧來搞定函數的執行順序外,還依靠任務隊列(task queue)來搞定另外一些代碼的執行。
一個線程中,事件循環是唯一的,但是任務隊列可以擁有多個。
任務隊列又分為macro-task(宏任務)與micro-task(微任務),在最新標準中,它們被分別稱為task與jobs。
macro-task大概包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering。
micro-task大概包括: process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)
setTimeout/Promise等我們稱之為任務源。而進入任務隊列的是他們指定的具體執行任務。
// setTimeout中的回調函數才是進入任務隊列的任務 setTimeout(function() { console.log("xxxx"); }) // 非常多的同學對于setTimeout的理解存在偏差。所以大概說一下誤解: // setTimeout作為一個任務分發器,這個函數會立即執行,而它所要分發的任務,也就是它的第一個參數,才是延遲執行
來自不同任務源的任務會進入到不同的任務隊列。其中setTimeout與setInterval是同源的。
其中每一個任務的執行,無論是macro-task還是micro-task,都是借助函數調用棧來完成。
事件循環執行循序事件循環的順序,決定了JavaScript代碼的執行順序。它從script(整體代碼)開始第一次循環。之后全局上下文進入函數調用棧。直到調用棧清空(只剩全局),然后執行所有的micro-task。當所有可執行的micro-task執行完畢之后,本輪循環結束。下一輪循環再次從macro-task開始,找到其中一個任務隊列執行完畢,然后再執行所有的micro-task,這樣一直循環下去。
當我們在執行setTimeout任務中遇到setTimeout時,它仍然會將對應的任務分發到setTimeout隊列中去,但是該任務就得等到下一輪事件循環執行。
那么整個事件循環中何時進行ui render呢?begin
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的內容呢?
根據HTML Standard,一輪事件循環執行結束之后,下輪事件循環執行之前開始進行UI render。即:macro-task任務執行完畢,接著執行完所有的micro-task任務后,此時本輪循環結束,開始執行UI render。UI render完畢之后接著下一輪循環。
在chrome瀏覽器中執行以上代碼,控制臺先輸出promise1,promise2,global1,then1(micro-task任務輸出),彈出"ui 開始渲染"警告框,點擊確定之后,頁面中的"begin"變為"end",再彈出警告框"ui 已經渲染完畢了嗎?" ,點擊確認之后再輸入timeout1.
1begin
// 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 入 JS stack
打印出 click
將 timeout 壓入到 macrotask
將 promise 壓入到 microtask
修改 outer 屬性 data-random
將 mutate 壓入到 microtask,
onclick 出 JS stack
此時,由于用戶點擊事件onclick產生的macrotask執行完畢,JS stack 清空,開始執行microtask.
promise 入 JS stack
打印出 promise
promise 出 JS stack
mutate 入 JS stack
打印出 mutate
mutate 出 JS stack
此時,microtask 執行完畢,JS stack 清空,但是由于事件冒泡,接著執行outer上的onclick事件.
onclick 入 JS stack
打印出 click
將 timeout 壓入到 macrotask
將 promise 壓入到 microtask
修改 outer 屬性 data-random
將 mutate 壓入到 microtask,
onclick 出 JS stack
此時,由于outer上的onclick事件產生的macrotask執行完畢,JS stack 清空,開始執行microtask.
promise 入 JS stack
打印出 promise
promise 出 JS stack
mutate 入 JS stack
打印出 mutate
mutate 出 JS stack
此時,本輪事件循環結束,UI 開始 render.
頁面中inner的innerHTML變為end
此時,UI render 完畢,開始下一輪事件循環.
timeout 入 JS stack
彈出警告 錨點.
打印出 timeout
timeout 出 JS stack
timeout 入 JS stack
彈出警告 錨點.
打印出 timeout
timeout 出 JS stack
到此為止,整個事件執行完畢,我們可以看到在彈出警告框之前inner的內容已經改變。
inner.addEventListener("click", onClick); outer.addEventListener("click", onClick); inner.click();
此時的執行順序是:
首先是script(整體代碼)入 JS stack
onclick 入 JS stack
打印出 click
將 timeout 壓入到 macrotask
將 promise 壓入到 microtask
修改 outer 屬性 data-random
將 mutate 壓入到 microtask,
onclick 出 JS stack
此時,inner 的 onclick 已經出 JS stack,但是script(整體代碼)還沒有出 JS stack,還不能執行microtask,由于冒泡,接著執行 outer 的 onclick.
onclick 入 JS stack
打印出 click
將 timeout 壓入到 macrotask
將 promise 壓入到 microtask
修改 outer 屬性 data-random
接著執行的outer.setAttribute("data-random", Math.random());,但是由于上一個mutation microtask還處于等待狀態,不能再添加mutation microtask,所以這里不會將 mutate 壓入到 microtask。接著執行:
onclick 出 JS stack
script(整體代碼)出 JS stack
此時,inner.click()執行完畢,script(整體代碼)已出 JS stack,JS stack 清空,開始執行mircotask.
promise 入 JS stack
打印出 promise
promise 出 JS stack
mutate 入 JS stack
打印出 mutate
mutate 出 JS stack
promise 入 JS stack
打印出 promise
promise 出 JS stack
此時,所有的mircotask執行完畢,本輪事件循環結束,UI 開始 render.
頁面中inner的innerHTML變為end
此時,UI render 完畢,開始下一輪事件循環.
timeout 入 JS stack
彈出警告 錨點.
打印出 timeout
timeout 出 JS stack
timeout 入 JS stack
彈出警告 錨點.
打印出 timeout
timeout 出 JS stack
到此為止,整個事件執行完畢,我們可以看到在彈出警告框之前inner的內容已經改變。
總結:首先執行macrotask,當js stack為空時執行microtask,接著開始UI render,接著再開始下一輪循環
參考文獻:
深入核心,詳解事件循環機制
Tasks, microtasks, queues and schedules
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/107342.html
摘要:為什么叫按照官網的解釋在下次更新循環結束之后執行延遲回調。在修改數據之后立即使用這個方法,獲取更新后的。在下個事件循環執行時確實是最新的了,但是回調并沒有在下個事件循環執行。 前言 在這之前我是沒有怎么看過vue源碼的,但是看了源碼后又產生了一些疑問,如果不看源碼我還真沒有任何疑問的去用nextTick,因為我只知道我想獲取更新后的dom我就在里面寫回調,只管寫準沒錯,有天好奇調試了下...
摘要:而當響應成功了以后,瀏覽器的事件表則會將回調函數添加至事件隊列中等待執行。事件循環器會不停的檢查事件隊列,如果不為空,則取出隊首壓入執行棧執行。類型的任務目前包括了以及的回調函數。 事件循環(event loop) : 首先說事件隊列(task queue) 事件隊列是一個存儲著待執行任務的隊列,其中的任務嚴格按照時間先后順序執行,排在隊頭的任務將會率先執行,而排在隊尾的任務會最后執行...
摘要:事件觸發線程主要負責將準備好的事件交給引擎線程執行。進程瀏覽器渲染進程瀏覽器內核,主要負責頁面的渲染執行以及事件的循環。第二輪循環結束。 將自己讀到的比較好的文章分享出來,大家互相學習,各位大佬有好的文章也可以留個鏈接互相學習,萬分感謝! 線程與進程 關于線程與進程的關系可以用下面的圖進行說明: showImg(https://segmentfault.com/img/bVbjSZt?...
閱讀 1891·2021-11-11 16:55
閱讀 2095·2021-10-08 10:13
閱讀 752·2019-08-30 11:01
閱讀 2162·2019-08-29 13:19
閱讀 3288·2019-08-28 18:18
閱讀 2626·2019-08-26 13:26
閱讀 586·2019-08-26 11:40
閱讀 1877·2019-08-23 17:17