event loop事件循環(huán)機制

事件循環(huán)分為兩種,分別是瀏覽器事件循環(huán)和node.js事件循環(huán),本文主要對瀏覽器事件循環(huán)進(jìn)行描述恭取。
我們都知道JavaScript是一門單線程語言,指主線程只有一個咽筋。Event Loop事件循環(huán)距误,其實就是JS引擎管理事件執(zhí)行的一個流程宪拥,具體由運行環(huán)境確定信夫。目前JS的主要運行環(huán)境有兩個窃蹋,瀏覽器和Node.js

Event loop

瀏覽器用來協(xié)調(diào)事件,用戶交互静稻,腳本警没,渲染,網(wǎng)絡(luò)的一種內(nèi)部機制振湾。

瀏覽器的事件循環(huán)分為同步任務(wù)和異步任務(wù)杀迹;所有同步任務(wù)都在call stack上執(zhí)行,形成一個函數(shù)調(diào)用棧(執(zhí)行棧)恰梢,而異步則先放到任務(wù)隊列(task queue)里佛南,任務(wù)隊列又分為宏任務(wù)(macro-task)與微任務(wù)(micro-task)。

call stack(LIFO后進(jìn)先出)

同步代碼的調(diào)用棧

  • 每調(diào)用一個函數(shù)嵌言,解釋器就會把該函數(shù)的執(zhí)行上下文添加到調(diào)用棧并開始執(zhí)行;
  • 正在調(diào)用棧中執(zhí)行的函數(shù)及穗,如果還調(diào)用了其他函數(shù)摧茴,那么新函數(shù)也會被添加到調(diào)用棧,并立即執(zhí)行埂陆;
  • 當(dāng)前函數(shù)執(zhí)行完畢后苛白,解釋器會將其執(zhí)行上下文清除調(diào)用棧,繼續(xù)執(zhí)行剩余執(zhí)行上下文中的剩余代碼焚虱;
  • 但分配的調(diào)用椆喝梗空間被占滿,會引發(fā)”堆棧溢出“的報錯鹃栽。
function a() {
    console.log('a');
}

function b() {
    console.log('b');
}

function c() {
    console.log('c');
    a();
    b();
}

c();

/**
* 輸出結(jié)果:c a b
*/

執(zhí)行該段代碼的時候躏率,首先調(diào)用函數(shù)c()。因此function c() {}會被放入調(diào)用棧中,然后開始執(zhí)行函數(shù)c薇芝,執(zhí)行的第一個語句是console.log('c')蓬抄,因此解釋器也會將其放入調(diào)用棧中。當(dāng)console執(zhí)行完夯到,控制臺打印后嚷缭,調(diào)用棧會將其移除。接著調(diào)用a()耍贾,同理阅爽。當(dāng)b()執(zhí)行完畢,此時c()也算結(jié)束了荐开,調(diào)用棧將其移出优床。

task queue

一系列(異步)task的集合

注意:
task queue在數(shù)據(jù)結(jié)構(gòu)是一個集合,并不是一個隊列誓焦,因為事件循環(huán)處理模型會從選定的task queue獲取第一個可運行的任務(wù)胆敞,而不是順序上的第一個任務(wù)

同步任務(wù)的執(zhí)行,其實就是跟前面那個案例一樣杂伟,按照代碼順序和調(diào)用順序移层,支持進(jìn)入調(diào)用棧中并執(zhí)行,執(zhí)行結(jié)束后就移除調(diào)用棧赫粥。
而異步任務(wù)的執(zhí)行观话,首先它依舊會進(jìn)入調(diào)用棧中,然后發(fā)起調(diào)用越平,然后解釋器會將其響應(yīng)回調(diào)任務(wù)放入一個任務(wù)隊列频蛔,緊接著調(diào)用棧會將這個任務(wù)移除。當(dāng)主線程清空后秦叛,即所有同步任務(wù)結(jié)束后晦溪,解釋器會讀取任務(wù)隊列,并依次將已完成的異步任務(wù)加入調(diào)用棧中并執(zhí)行挣跋。

這里有個重點三圆,就是異步任務(wù)不是直接進(jìn)入任務(wù)隊列的。

宏任務(wù)和微任務(wù)都屬于異步任務(wù)避咆,它們的區(qū)別在于它們的執(zhí)行迅速舟肉。

  1. 宏任務(wù)macrotask(Task)
    依次進(jìn)入task queue,包括:
  • script(整塊代碼)
  • setTimeout
  • setInterval
  • I/O
  • UI rendering
  • setImmediate(node環(huán)境)
  • requestAnimationFrame(瀏覽器獨有)
  • requestIdleCallback(瀏覽器獨有)
  1. 微任務(wù)microtask(Jobs)
    是真的隊列(FIFO先進(jìn)先出隊列)
    包括:
  • new promise().then(回調(diào))
  • MutationObserver(html5新特性查库,用于監(jiān)聽dom樹的變化)
  • process.nextTick(node環(huán)境)
  • async await(語法糖)

實宏任務(wù)隊列和微任務(wù)隊列的執(zhí)行路媚,就是事件循環(huán)的一部分了,所以放在這里一起說樊销。

事件循環(huán)的具體流程如下:

  1. 從宏任務(wù)隊列中整慎,按照入隊順序脏款,找到第一個執(zhí)行的宏任務(wù),放入調(diào)用棧院领,開始執(zhí)行弛矛;
  2. 執(zhí)行完該宏任務(wù)下所有同步任務(wù)后,即調(diào)用棧清空后比然,該宏任務(wù)被推出宏任務(wù)隊列丈氓,然后微任務(wù)隊列開始按照入隊順序,依次執(zhí)行其中的微任務(wù)强法,直至微任務(wù)隊列清空為止万俗;
  3. 當(dāng)微任務(wù)隊列清空后,一個事件循環(huán)結(jié)束饮怯;
  4. 接著從宏任務(wù)隊列中闰歪,找到下一個執(zhí)行的宏任務(wù),開始第二個事件循環(huán)蓖墅,直至宏任務(wù)隊列清空為止库倘。

這里有幾個重點:

  • 當(dāng)我們第一次執(zhí)行的時候,解釋器會將整體代碼script放入宏任務(wù)隊列中论矾,因此事件循環(huán)是從第一個宏任務(wù)開始的教翩;
  • 如果在執(zhí)行微任務(wù)的過程中,產(chǎn)生新的微任務(wù)添加到微任務(wù)隊列中贪壳,也需要一起清空饱亿;微任務(wù)隊列沒清空之前,是不會執(zhí)行下一個宏任務(wù)的闰靴。
console.log("a");

setTimeout(function () {
    console.log("b");
}, 0);

new Promise((resolve) => {
    console.log("c");
    resolve();
})
    .then(function () {
        console.log("d");
    })
    .then(function () {
        console.log("e");
    });

console.log("f");

/**
* 輸出結(jié)果:a c f d e b
*/

首先彪笼,當(dāng)代碼執(zhí)行的時候,整體代碼script被推入宏任務(wù)隊列中蚂且,并開始執(zhí)行該宏任務(wù)配猫。

按照代碼順序,首先執(zhí)行console.log("a")膘掰。

該函數(shù)上下文被推入調(diào)用棧章姓,執(zhí)行完后,即移除調(diào)用棧识埋。

接下來執(zhí)行setTimeout(),該函數(shù)上下文也進(jìn)入調(diào)用棧中零渐。

因為setTimeout是一個宏任務(wù)窒舟,因此將其callback函數(shù)推入宏任務(wù)隊列中,然后該函數(shù)就被移除調(diào)用棧诵盼,繼續(xù)往下執(zhí)行惠豺。

緊接著是Promise語句银还,先將其放入調(diào)用棧,然后接著往下執(zhí)行洁墙。

執(zhí)行console.log("c")resolve()蛹疯,這里就不多說了。

接著來到new Promise().then()方法热监,這是一個微任務(wù)捺弦,因此將其推入微任務(wù)隊列中。

這時new Promise語句已經(jīng)執(zhí)行結(jié)束了孝扛,就被移除調(diào)用棧列吼。

接著做執(zhí)行console.log('f')

這時候苦始,script宏任務(wù)已經(jīng)執(zhí)行結(jié)束了寞钥,因此被推出宏任務(wù)隊列。

緊接著開始清空微任務(wù)隊列了陌选。首先執(zhí)行的是Promise then理郑,因此它被推入調(diào)用棧中。

然后開始執(zhí)行其中的console.log("d")咨油。

執(zhí)行結(jié)束后您炉,檢測到后面還有一個then()函數(shù),因此將其推入微任務(wù)隊列中臼勉。

此時第一個then()函數(shù)已經(jīng)執(zhí)行結(jié)束了邻吭,就會移除調(diào)用棧和微任務(wù)隊列。

此時微任務(wù)隊列還沒被清空宴霸,因此繼續(xù)執(zhí)行下一個微任務(wù)囱晴。

執(zhí)行過程跟前面差不多,就不多說了瓢谢。

此時微任務(wù)隊列已經(jīng)清空了畸写,第一個事件循環(huán)已經(jīng)結(jié)束了。

接下來執(zhí)行下一個宏任務(wù)氓扛,即setTimeout callback枯芬。

執(zhí)行結(jié)束后,它也被移除宏任務(wù)隊列和調(diào)用棧采郎。

這時候微任務(wù)隊列里面沒有任務(wù)千所,因此第二個事件循環(huán)也結(jié)束了。

宏任務(wù)也被清空了蒜埋,因此這段代碼已經(jīng)執(zhí)行結(jié)束了淫痰。

await

async關(guān)鍵字是將一個同步函數(shù)變成一個異步函數(shù),并將返回值變?yōu)閜romise整份。
而await可以放在任何異步的待错、基于promise的函數(shù)之前籽孙。在執(zhí)行過程中,它會暫停代碼在該行上火俄,直到promise完成犯建,然后返回結(jié)果值。而在暫停的同時瓜客,其他正在等待執(zhí)行的代碼就有機會執(zhí)行了

async function async1() {
    console.log("a");
    const res = await async2();
    console.log("b");
}

async function async2() {
    console.log("c");
    return 2;
}

console.log("d");

setTimeout(() => {
    console.log("e");
}, 0);

async1().then(res => {
    console.log("f")
})

new Promise((resolve) => {
    console.log("g");
    resolve();
}).then(() => {
    console.log("h");
});

console.log("i");

/**
* 輸出結(jié)果:d a c g i b h f e 
*/

關(guān)于更多await和頁面渲染具體運行動圖适瓦,可以查看https://juejin.cn/post/6969028296893792286#heading-9,作者寫的很詳細(xì)忆家,容易理解
此文多處摘抄于此犹菇。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市芽卿,隨后出現(xiàn)的幾起案子揭芍,更是在濱河造成了極大的恐慌,老刑警劉巖卸例,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件称杨,死亡現(xiàn)場離奇詭異,居然都是意外死亡筷转,警方通過查閱死者的電腦和手機姑原,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呜舒,“玉大人锭汛,你說我怎么就攤上這事∠龋” “怎么了唤殴?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長到腥。 經(jīng)常有香客問我朵逝,道長,這世上最難降的妖魔是什么乡范? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任配名,我火速辦了婚禮,結(jié)果婚禮上晋辆,老公的妹妹穿的比我還像新娘渠脉。我一直安慰自己,他們只是感情好瓶佳,可當(dāng)我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布连舍。 她就那樣靜靜地躺著,像睡著了一般涩哟。 火紅的嫁衣襯著肌膚如雪索赏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天贴彼,我揣著相機與錄音潜腻,去河邊找鬼。 笑死器仗,一個胖子當(dāng)著我的面吹牛融涣,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播精钮,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼威鹿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了轨香?” 一聲冷哼從身側(cè)響起忽你,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎臂容,沒想到半個月后科雳,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡脓杉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年糟秘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片球散。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡尿赚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蕉堰,到底是詐尸還是另有隱情凌净,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布嘁灯,位于F島的核電站泻蚊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏丑婿。R本人自食惡果不足惜性雄,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望羹奉。 院中可真熱鬧秒旋,春花似錦、人聲如沸诀拭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耕挨。三九已至细卧,卻和暖如春尉桩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背贪庙。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工蜘犁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人止邮。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓这橙,卻偏偏與公主長得像,于是被迫代替她去往敵國和親导披。 傳聞我的和親對象是個殘疾皇子屈扎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內(nèi)容