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