摘要:而這些隊列由的事件循環(huán)來搞定宏任務(wù)與微任務(wù),在最新標準中,它們被分別稱為與。我們梳理一下事件循環(huán)的執(zhí)行機制循環(huán)首先從宏任務(wù)開始,遇到,生成執(zhí)行上下文,開始進入執(zhí)行棧,可執(zhí)行代碼入棧,依次執(zhí)行代碼,調(diào)用完成出棧。
寫在前面
js是一門單線程的編程語言,也就是說js在處理任務(wù)的時候,所有任務(wù)只能在一個線程上排隊被執(zhí)行,那如果某一個任務(wù)耗時比較長呢?總不能等到它執(zhí)行結(jié)束再去執(zhí)行下一個。
所以在線程之內(nèi),又被分為了兩個隊列:
同步任務(wù)隊列
異步任務(wù)隊列
舉個例子來說:比如你去銀行辦理業(yè)務(wù),都需要領(lǐng)號排隊。銀行柜員一個個辦理業(yè)務(wù),這時這個柜員就相當于一個js線程,客戶排的隊就相當于同步任務(wù)隊列,每個人對于柜員相當于一個個的任務(wù)。
但這個時候,你的電話突然響了,你去接電話接了半小時。這時候人家柜員一看你這情況,直接叫了下一個,而你領(lǐng)的號就作廢了,只能重新零號排隊。這時候你就是被分發(fā)到了異步任務(wù)隊列。
等你前邊的人都完事了,柜員把你叫過去辦了你的業(yè)務(wù),這時候就是同步隊列中的任務(wù)執(zhí)行完了,主線程會處理異步隊列中的任務(wù)。
這里說的異步任務(wù),它的意思是包含了獨立于主執(zhí)行棧之外的宏任務(wù)和微任務(wù)。
先看一個簡單的例子,對這樣的執(zhí)行機制有個簡單的認識:
console.log("start") console.log("end")
上邊的執(zhí)行結(jié)果大家肯定都明白,先輸出start,再輸出end,這一段代碼會進入同步隊列,順序執(zhí)行。
那么我們加點料:
console.log("start") setTimeout(function() { console.log("setTimeout") }, 0) console.log("end")
這樣的情況,函數(shù)調(diào)用棧執(zhí)行到setTimeout時,setTimeout會在規(guī)定的時間點將回調(diào)函數(shù)放入異步隊列,等待同步隊列的任務(wù)被執(zhí)行完,立即執(zhí)行,所以結(jié)果是:start、end、setTimeout。
但需要注意的一點是,普遍認為setTimeout定時執(zhí)行的認知是片面的,因為假設(shè)setTimeout規(guī)定2秒后執(zhí)行,但同步隊列中有一個函數(shù),執(zhí)行花了很長時間,甚至花了1秒。那么這時setTimeout中的回調(diào)也會等上至少1秒之后,同步任務(wù)都執(zhí)行完了,再去執(zhí)行。這時候的setTimeout回調(diào)執(zhí)行的時機就會超過2秒,也就是至少3秒。
宏任務(wù)與微任務(wù)宏任務(wù)與微任務(wù)都是獨立與主執(zhí)行棧之外的另外兩個隊列,可以在概念上劃分在異步任務(wù)隊列里。而這些隊列由js的事件循環(huán)(EventLoop)來搞定
macro-task(宏任務(wù))與micro-task(微任務(wù)),在最新標準中,它們被分別稱為task與jobs。
由于寫文章時沒有注意到,實際上宏任務(wù)與微任務(wù)的概念是不準確的,但由于文章中涉及多處宏任務(wù)、微任務(wù)的解讀,所以本文暫時還是用宏任務(wù)、微任務(wù)來分別代指task、jobs。但讀者要明白規(guī)范中沒有宏任務(wù)的概念,只有task與jobs
其中宏任務(wù)(task)包括:
script(整體代碼)
setTimeout, setInterval, setImmediate,
I/O
UI rendering
ajax請求不屬于宏任務(wù),js線程遇到ajax請求,會將請求交給對應(yīng)的http線程處理,一旦請求返回結(jié)果,就會將對應(yīng)的回調(diào)放入宏任務(wù)隊列,等請求完成執(zhí)行。
微任務(wù)(jobs)包括:
process.nextTick
Promise
Object.observe(已廢棄)
MutationObserver(html5新特性)
這些我們可以理解為它們在執(zhí)行上下文中都是可執(zhí)行代碼,會立即執(zhí)行,只不過會將各自的回調(diào)函數(shù)放入對應(yīng)的任務(wù)隊列中(宏任務(wù)微任務(wù)),也就相當于一個調(diào)度者。
我們梳理一下事件循環(huán)的執(zhí)行機制:
循環(huán)首先從宏任務(wù)開始,遇到script,生成執(zhí)行上下文,開始進入執(zhí)行棧,可執(zhí)行代碼入棧,依次執(zhí)行代碼,調(diào)用完成出棧。
執(zhí)行過程中遇到上邊提到的調(diào)度者,會同步執(zhí)行調(diào)度者,由調(diào)度者將其負責的任務(wù)(回調(diào)函數(shù))放到對應(yīng)的任務(wù)隊列中,直到主執(zhí)行棧清空,然后開始執(zhí)行微任務(wù)的任務(wù)隊列。微任務(wù)也清空后,再次從宏任務(wù)開始,一直循環(huán)這一過程。
上邊說了那么多,還是用一些代碼來驗證一下是否是這樣的,先來一個簡單一點的。
console.log("start") setTimeout(function() { console.log("timeout") }, 0) new Promise(function(resolve) { console.log("promise") resolve() }).then(function() { console.log("promise resolved") }) console.log("end")
根據(jù)上邊的結(jié)論,分析一下執(zhí)行過程:
建立執(zhí)行上下文,進入執(zhí)行棧開始執(zhí)行代碼,打印start
往下執(zhí)行,遇到setTimeout,將回調(diào)函數(shù)放入宏任務(wù)隊列,等待執(zhí)行
繼續(xù)往下,有個new Promise,其回調(diào)函數(shù)并不會被放入其他任務(wù)隊列,因此會同步地執(zhí)行,打印promise,但是當resolve后,.then會把其內(nèi)部的回調(diào)函數(shù)放入微任務(wù)隊列
執(zhí)行到了最底部的代碼,打印出end。這時,主執(zhí)行棧清空了,開始尋找微任務(wù)隊列里有沒有可執(zhí)行代碼
發(fā)現(xiàn)了微任務(wù)隊列中有之前放進去的代碼,執(zhí)行打印出promise resolved,第一次循環(huán)結(jié)束
再開始第二次循環(huán),從宏任務(wù)開始,檢查宏任務(wù)隊列是否有可執(zhí)行代碼,發(fā)現(xiàn)有一個,打印timeout
所以,打印順序是:start-->promise-->end-->promise resolved-->timeout
上邊是一個簡單示例,比較好理解。那么接下來看一個稍微復(fù)雜一點的(這里直接用漢字直觀地表明了打印的時機,避免看起來費勁):
console.log("第一次循環(huán)主執(zhí)行棧開始") setTimeout(function() { console.log("第二次循環(huán)開始,宏任務(wù)隊列的第一個宏任務(wù)執(zhí)行中") new Promise(function(resolve) { console.log("宏任務(wù)隊列的第一個宏任務(wù)的微任務(wù)繼續(xù)執(zhí)行") resolve() }).then(function() { console.log("第二次循環(huán)的微任務(wù)隊列的微任務(wù)執(zhí)行") }) }, 0) new Promise(function(resolve) { console.log("第一次循環(huán)主執(zhí)行棧進行中...") resolve() }).then(function() { console.log("第一次循環(huán)微任務(wù),第一次循環(huán)結(jié)束") setTimeout(function() { console.log("第二次循環(huán)的宏任務(wù)隊列的第二個宏任務(wù)執(zhí)行") }) }) console.log("第一次循環(huán)主執(zhí)行棧完成")
同樣我們分析一下執(zhí)行過程:
第一次循環(huán)
進入執(zhí)行棧執(zhí)行代碼,打印第一次循環(huán)主執(zhí)行棧開始
遇到setTimeout,將回調(diào)放入宏任務(wù)隊列等待執(zhí)行
promise聲明過程是同步的,打印第一次循環(huán)主執(zhí)行棧進行中...,resolve后遇到.then,將回調(diào)放入微任務(wù)隊列
打印第一次循環(huán)主執(zhí)行棧完成
檢查微任務(wù)隊列是否有可執(zhí)行代碼,有一個第三步放入的任務(wù),打印第一次循環(huán)微任務(wù),第一次循環(huán)結(jié)束,第一次循環(huán)結(jié)束,同時遇到setTimeout,將回調(diào)放入宏任務(wù)隊列
第二次循環(huán)
從宏任務(wù)入手,檢查宏任務(wù)隊列,發(fā)現(xiàn)有兩個宏任務(wù),分別是第一次循環(huán)第二步和第一次循環(huán)第五步被放入的任務(wù),先執(zhí)行第一個宏任務(wù),打印第二次循環(huán)開始,宏任務(wù)隊列的第一個宏任務(wù)執(zhí)行中
遇到promise聲明語句,打印宏任務(wù)隊列的第一個宏任務(wù)繼續(xù)執(zhí)行,這時候又被resolve了,又會將.then中的回調(diào)放入微任務(wù)隊列,這是這個宏任務(wù)隊列中的第一個任務(wù)還沒執(zhí)行完
第一個宏任務(wù)中的同步代碼執(zhí)行完畢,檢查微任務(wù)隊列,發(fā)現(xiàn)有一段第二步放進去的代碼,執(zhí)行打印第二次循環(huán)的微任務(wù)隊列的微任務(wù)執(zhí)行,此時第一個宏任務(wù)執(zhí)行完畢
開始執(zhí)行第二個宏任務(wù),打印第二次循環(huán)的宏任務(wù)隊列的第二個宏任務(wù)執(zhí)行,所有任務(wù)隊列全部清空,執(zhí)行完畢
所以打印順序為:
第一次循環(huán)主執(zhí)行棧開始
第一次循環(huán)主執(zhí)行棧進行中...
第一次循環(huán)主執(zhí)行棧完成
第一次循環(huán)微任務(wù),第一次循環(huán)結(jié)束
第二次循環(huán)開始,宏任務(wù)隊列的第一個宏任務(wù)執(zhí)行中
第二次循環(huán)的宏任務(wù)隊列的第一個宏任務(wù)的微任務(wù)繼續(xù)執(zhí)行
第二次循環(huán)的微任務(wù)隊列的微任務(wù)執(zhí)行
第二次循環(huán)的宏任務(wù)隊列的第二個宏任務(wù)執(zhí)行
看一下gif,事件循環(huán)以肉眼可見的形式呈現(xiàn)出來(兩次循環(huán)之間有微小的時間間隔)
總結(jié)js的執(zhí)行機制是面試中常考的點,也是非常繞的。但相信完全了解事件循環(huán)機制,仔細分析的話,面試遇到這樣的題完全不是問題。我在寫這篇文章的時候,發(fā)現(xiàn)自己之前理解的很大一部分是錯的。如果大家覺得哪里有錯誤,還請幫忙指點出來。
歡迎關(guān)注我的公眾號: 一口一個前端,不定期分享我所理解的前端知識
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/106108.html
摘要:瀏覽器是多進程的,而瀏覽器的內(nèi)核渲染進程是多線程的。如果已經(jīng)將回調(diào)函數(shù)放進任務(wù)隊列,但是主線程正在執(zhí)行一個非常耗時的任務(wù),當這個任務(wù)執(zhí)行完畢后,主線程去任務(wù)隊列中取任務(wù),這個時候,就會出現(xiàn)連續(xù)執(zhí)行的情況,也就是說相當于失效了。 前言 ??在刷筆試題的時候,經(jīng)常會碰到setTimeout的問題,只知道這個是設(shè)置定時器;但是考察的重點一般是在一個方法中包含了定時器,定時器中的打印和方法中打...
摘要:圖片轉(zhuǎn)引自的演講和兩個定時器中回調(diào)的執(zhí)行邏輯便是典型的機制。異步編程關(guān)于異步編程我的理解是,在執(zhí)行環(huán)境所提供的異步機制之上,在應(yīng)用編碼層面上實現(xiàn)整體流程控制的異步風格。 問題背景 在一次開發(fā)任務(wù)中,需要實現(xiàn)如下一個餅狀圖動畫,基于canvas進行繪圖,但由于對于JS運行環(huán)境中異步機制的不了解,所以遇到了一個棘手的問題,始終無法解決,之后在與同事交流之后才恍然大悟。問題的根節(jié)在于經(jīng)典的J...
摘要:事件觸發(fā)線程主要負責將準備好的事件交給引擎線程執(zhí)行。進程瀏覽器渲染進程瀏覽器內(nèi)核,主要負責頁面的渲染執(zhí)行以及事件的循環(huán)。第二輪循環(huán)結(jié)束。 將自己讀到的比較好的文章分享出來,大家互相學習,各位大佬有好的文章也可以留個鏈接互相學習,萬分感謝! 線程與進程 關(guān)于線程與進程的關(guān)系可以用下面的圖進行說明: showImg(https://segmentfault.com/img/bVbjSZt?...
摘要:事件觸發(fā)線程主要負責將準備好的事件交給引擎線程執(zhí)行。進程瀏覽器渲染進程瀏覽器內(nèi)核,主要負責頁面的渲染執(zhí)行以及事件的循環(huán)。第二輪循環(huán)結(jié)束。 將自己讀到的比較好的文章分享出來,大家互相學習,各位大佬有好的文章也可以留個鏈接互相學習,萬分感謝! 線程與進程 關(guān)于線程與進程的關(guān)系可以用下面的圖進行說明: showImg(https://segmentfault.com/img/bVbjSZt?...
閱讀 1246·2021-09-04 16:41
閱讀 2403·2021-09-02 10:18
閱讀 917·2019-08-29 16:40
閱讀 2613·2019-08-29 16:14
閱讀 898·2019-08-26 13:41
閱讀 1298·2019-08-26 12:24
閱讀 731·2019-08-26 10:24
閱讀 2868·2019-08-23 17:54