JavaScript特點是”非阻塞“眼刃,V8引擎通過事件循環(huán)event loop
來實現(xiàn)這一特點而咆。
1. 執(zhí)行棧與事件隊列
當javaScript代碼執(zhí)行的時候會將不同的變量存于內(nèi)存中的不同位置:堆heap
和棧stack
中來加以區(qū)分。其中烟零, 堆里存放著一些對象瘪松。而棧中則存放這一些基礎型變量以及對象指針。
當調(diào)用一個方法時瓶摆, JS會生成一個與這個方法對應的執(zhí)行環(huán)境context
,又叫執(zhí)行上下文凉逛。 這個執(zhí)行環(huán)境中有這個方法的私有作用域, 上層作用域的指向群井, 方法的參數(shù)状飞, 這個作用域中定義的變量以及這個作用域的this對象。 而依次調(diào)用的時候书斜, 因為JS是單線程的诬辈, 同一時間只能執(zhí)行一個方法, 于是這些方法被排隊在一個單獨的地方荐吉。 這個地方被稱為執(zhí)行棧焙糟。
當一個腳本第一次執(zhí)行的時候, JS引擎會解析這段代碼样屠, 并將其中的同步代碼
按照執(zhí)行順序加入執(zhí)行棧中穿撮, 然后從頭開始執(zhí)行。如果當前執(zhí)行的是一個方法痪欲。 當這個執(zhí)行環(huán)境中的代碼執(zhí)行完畢并返回結(jié)果后悦穿, js會退出這個執(zhí)行環(huán)境并把這個執(zhí)行環(huán)境銷毀, 回到上一個方法的執(zhí)行環(huán)境业踢。 ......這個過程反復進行栗柒, 直到執(zhí)行棧中的代碼全部執(zhí)行完畢。
global初次運行腳本時向執(zhí)行棧中加入代碼:
上圖:一個方法執(zhí)行會向執(zhí)行棧中加入這個方法的執(zhí)行環(huán)境知举, 在這個執(zhí)行環(huán)境中還可以調(diào)用其他方法瞬沦, 甚至是自己太伊, 其結(jié)果不過是在執(zhí)行棧中再添加一個執(zhí)行環(huán)境。 這個過程可以是無限進行下去的逛钻,除非發(fā)生了棧溢出僚焦, 即超出了所能使用內(nèi)存的最大值。
當一個異步代碼(如發(fā)送ajax請求數(shù)據(jù))執(zhí)行后會如何呢绣的? 開頭說叠赐, js的一大特點是 非阻塞
, 實現(xiàn)這一點的關鍵在于一項機制——事件隊列Task Queue
js引擎遇到一個異步事件后并不會一直等待其返回結(jié)果屡江, 而是會將這個事件掛起(pending)芭概, 繼續(xù)執(zhí)行執(zhí)行棧中的其他任務。 當一個異步事件返回結(jié)果后惩嘉, js會將這個事件加入與當前執(zhí)行棧不同的另一個隊列罢洲, 我們稱之為。 被放入事件隊列的不會立刻執(zhí)行其回調(diào)文黎。 而是等待當前執(zhí)行棧中的所有任務都執(zhí)行完畢惹苗, 主線程處于閑置狀態(tài)時, 主線程會去查找事件隊列是否有任務耸峭。 如果有桩蓉, 那么主線程會取出排在第一位的事件, 并把這個事件對應的回調(diào)放入執(zhí)行棧中劳闹, 然后執(zhí)行其中的同步代碼......, 如此反復院究, 這樣就形成了一個無限循環(huán)。 這個過程被稱之為”事件循環(huán)
Event Loop
“本涕。
過程圖片如下:
stack :表示我們所說的執(zhí)行棧
web apis: 代表一些異步事件
callback queue: 事件隊列业汰。
2. macro task宏任務
和 micro task微任務
以上的事件循環(huán)過程是一個宏觀的表述, 實際上因為異步任務之間并不相同菩颖, 因此他們的執(zhí)行優(yōu)先級也有區(qū)別样漆。 不同的異步任務分為兩大類: 微任務 micro task
和 宏任務 macro task
。
以下事件屬于宏任務:
setInterval()
setTimeout()
以下事件屬于微任務:
new Promise()
new MutaionObserver()
前面介紹了晦闰,在一個事件循環(huán)中放祟, 異步事件返回結(jié)果后會被放到一個任務隊列中。 然而呻右,根據(jù)這個異步事件的類型舞竿, 這個事件實際上會劃分到對應的宏任務隊列和微任務隊列中去。 并且在當前執(zhí)行棧為空的時候窿冯, 主線程會查看微任務隊列中是否有事件存在。 如果存在确徙,則會依次執(zhí)行隊列中事件對應的回調(diào)醒串,直到微任務隊列為空执桌。 然后去宏任務隊列中取出最前面的一個事件, 把對應的回調(diào)加入當前執(zhí)行棧... 反復如此芜赌, 進入循環(huán)仰挣。
例子:
setTimeout(function() {
console.log(1);
})
new Promise(function(resolve, reject) {
console.log(2);
resolve(3);
}).then(function(res) => {
console.log(res);
})
結(jié)果為: 2 3 1
注意: 當 當前執(zhí)行棧執(zhí)行完畢時會優(yōu)先處理所有微任務任務隊列中的事件, 然后再去宏任務隊列中取出一個事件缠沈。 同一次事件循環(huán)中膘壶, 微任務永遠在宏任務之前執(zhí)行