Js事件循環(huán)(Event Loop)機(jī)制

前言

Event Loop是計(jì)算機(jī)系統(tǒng)的一種運(yùn)行機(jī)制遭赂,是個(gè)很重要的概念淹冰。而Javascript用這種機(jī)制來(lái)解決單線程運(yùn)行帶來(lái)的問(wèn)題逢净。理解很熟悉將會(huì)有利于我們更容易理解Vue的異步事件毁习。

JavaScript是單線程的

1烤送、什么是單線程寒随?

單線程在程序執(zhí)行時(shí),所走的程序路徑按照連續(xù)順序排下來(lái)帮坚,前面的必須處理好妻往,后面的才會(huì)執(zhí)行。簡(jiǎn)單來(lái)說(shuō)试和,即同一時(shí)間只能做一件事件讯泣。

2、Js為什么是單線程阅悍?

Js是一種運(yùn)行在網(wǎng)頁(yè)的簡(jiǎn)單的腳本語(yǔ)言好渠,由于設(shè)計(jì)的初衷是作為瀏覽器腳本語(yǔ)言,用于與用戶互動(dòng)溉箕,以及操作DOM晦墙。這決定它是單線程的。

3肴茄、單線程帶來(lái)的問(wèn)題晌畅?

單線程就意味著,所有任務(wù)都需要排隊(duì)寡痰,前一個(gè)任務(wù)結(jié)束抗楔,才會(huì)執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng)拦坠,后一個(gè)任務(wù)就需要一直等著连躏。這就會(huì)導(dǎo)致IO操作(耗時(shí)但cpu閑置)時(shí)造成性能浪費(fèi)的問(wèn)題。

4贞滨、如何解決單線程的性能問(wèn)題入热?

采用異步可以解決拍棕。主線程完全可以不管IO操作,暫時(shí)掛起處于等待中的任務(wù)勺良,先運(yùn)行排在后面的任務(wù)绰播。等到IO操作返回了結(jié)果,再回過(guò)頭尚困,把掛起的任務(wù)繼續(xù)執(zhí)行下去蠢箩。于是,所有任務(wù)可以分成兩種事甜,一種是同步任務(wù)谬泌,另一種是異步任務(wù)。

執(zhí)行棧

當(dāng)Javascript代碼執(zhí)行的時(shí)候會(huì)將不同的變量存于內(nèi)存中的不同位置:堆(heap)和棧(stack)中來(lái)加以區(qū)分逻谦。其中掌实,堆里存放著一些對(duì)象。而棧中則存放著一些基礎(chǔ)類型變量以及對(duì)象的指針邦马。但是我們這里說(shuō)的執(zhí)行棧和上面這個(gè)棧的意義卻有些不同潮峦。

js 在執(zhí)行可執(zhí)行的腳本時(shí),會(huì)經(jīng)過(guò)以下步驟:

  1. 首先會(huì)創(chuàng)建一個(gè)全局可執(zhí)行上下文globalContext勇婴,每當(dāng)執(zhí)行到一個(gè)函數(shù)調(diào)用時(shí)都會(huì)創(chuàng)建一個(gè)可執(zhí)行上下文(execution context)EC忱嘹。
  2. 可執(zhí)行程序可能會(huì)存在很多函數(shù)調(diào)用,那么就會(huì)創(chuàng)建很多EC耕渴,所以 JavaScript 引擎創(chuàng)建了執(zhí)行上下文棧(Execution context stack拘悦,ECS)來(lái)管理執(zhí)行上下文。
  3. 當(dāng)函數(shù)調(diào)用完成橱脸,Js會(huì)退出這個(gè)執(zhí)行環(huán)境并把這個(gè)執(zhí)行環(huán)境銷毀础米,回到上一個(gè)方法的執(zhí)行環(huán)境。 這個(gè)過(guò)程反復(fù)進(jìn)行添诉,直到執(zhí)行棧中的代碼全部執(zhí)行完畢屁桑。

image

實(shí)例

function fun3() {
    console.log('fun3')
}
function fun2() {
    fun3();
}
function fun1() {
    fun2();
}
fun1();

當(dāng)執(zhí)行一個(gè)函數(shù)的時(shí)候,就會(huì)創(chuàng)建一個(gè)執(zhí)行上下文栏赴,并且壓入執(zhí)行上下文棧蘑斧,當(dāng)函數(shù)執(zhí)行完畢的時(shí)候,就會(huì)將函數(shù)的執(zhí)行上下文從棧中彈出须眷。知道了這樣的工作原理竖瘾,讓我們來(lái)看看如何處理上面這段代碼:

1.執(zhí)行全局代碼,創(chuàng)建全局執(zhí)行上下文花颗,全局上下文被壓入執(zhí)行上下文棧

ECStack = [
    globalContext
];
  1. 全局上下文初始化
   globalContext = {
        VO: [global],
        Scope: [globalContext.VO],
        this: globalContext.VO
    }
  1. 初始化的同時(shí)捕传,fun1函數(shù)被創(chuàng)建,保存作用域鏈到函數(shù)的內(nèi)部屬性[[scope]]
 fun1.[[scope]] = [
      globalContext.VO
    ];
  1. 執(zhí)行 fun1 函數(shù)扩劝,創(chuàng)建fun1函數(shù)執(zhí)行上下文庸论,fun1函數(shù)執(zhí)行上下文被壓入執(zhí)行上下文棧
 ECStack = [
        fun1,
        globalContext
    ];
  1. fun1函數(shù)執(zhí)行上下文初始化:

    1.復(fù)制函數(shù) [[scope]] 屬性創(chuàng)建作用域鏈职辅。

    2.用 arguments 創(chuàng)建活動(dòng)對(duì)象。

    3.初始化活動(dòng)對(duì)象聂示,即加入形參罐农、函數(shù)聲明、變量聲明催什。

    4.將活動(dòng)對(duì)象壓入fun1 作用域鏈頂端。
    同時(shí) f 函數(shù)被創(chuàng)建宰睡,保存作用域鏈到 f 函數(shù)的內(nèi)部屬性[[scope]]

  checkscopeContext = {
        AO: {
            arguments: {
                length: 0
            },
            scope: undefined,
            f: reference to function f(){}
        },
        Scope: [AO, globalContext.VO],
        this: undefined
    }
  1. 執(zhí)行 fun2() 函數(shù)蒲凶,重復(fù)步驟2。
  2. 最終形成這樣的執(zhí)行棧:
   ECStack = [
        fun3
        fun2,
        fun1,
        globalContext
    ];
  1. fun3執(zhí)行完畢拆内,從執(zhí)行棧中彈出...一直到fun1

事件循環(huán)(Event Loop)

JavaScript內(nèi)存模型

在了解事件循環(huán)之前旋圆,先要弄明白Js的內(nèi)存模型,這有助于更好的理解事件循環(huán)麸恍。

  • 調(diào)用棧(Call Stack):用于主線程任務(wù)的執(zhí)行灵巧。
  • 堆(Heap):用于存放非結(jié)構(gòu)數(shù)據(jù),如程序分配的變量和對(duì)象抹沪。
  • 任務(wù)隊(duì)列(Queue): 用于存放異步任務(wù)刻肄。

Js異步執(zhí)行的運(yùn)行機(jī)制

  1. 所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧融欧。
  2. 主線程之外敏弃,還存在一個(gè)任務(wù)隊(duì)列。只要異步任務(wù)有了運(yùn)行結(jié)果噪馏,就在任務(wù)隊(duì)列之中放置一個(gè)事件麦到。
  3. 一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取任務(wù)隊(duì)列欠肾,看看里面有哪些事件瓶颠。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài)刺桃,進(jìn)入執(zhí)行棧粹淋,開始執(zhí)行。
  4. 主線程不斷重復(fù)上面的第三步瑟慈。

任務(wù)

異步任務(wù)存放在任務(wù)隊(duì)列里廓啊,異步任務(wù)分為 宏任務(wù)(macrotask)與微任務(wù)(microtask),不同的API注冊(cè)的任務(wù)會(huì)依次進(jìn)入自身對(duì)應(yīng)的隊(duì)列中封豪,然后等待Event Loop將它們依次壓入執(zhí)行棧中執(zhí)行谴轮。

宏任務(wù)主要包含:

  • script(整體代碼)
  • setTimeout
  • setInterval
  • I/OUI交互事件
  • setImmediate(Node.js 環(huán)境)

微任務(wù)主要包含:

  • Promise
  • MutaionObserver
  • process.nextTick(Node.js 環(huán)境)

我們的JavaScript的執(zhí)行過(guò)程是單線程的吹埠,所有的任務(wù)可以看做存放在兩個(gè)隊(duì)列中——執(zhí)行隊(duì)列和事件隊(duì)列第步。

執(zhí)行隊(duì)列里面是所有同步代碼的任務(wù)谴忧,事件隊(duì)列里面是所有異步代碼的宏任務(wù)聚谁,而我們的微任務(wù),是處在兩個(gè)隊(duì)列之間。

當(dāng)JavaScript執(zhí)行時(shí)迄委,優(yōu)先執(zhí)行完所有同步代碼,遇到對(duì)應(yīng)的異步代碼窝革,就會(huì)根據(jù)其任務(wù)類型存到對(duì)應(yīng)隊(duì)列(宏任務(wù)放入事件隊(duì)列姻蚓,微任務(wù)放入執(zhí)行隊(duì)列之后,事件隊(duì)列之前)堆生;當(dāng)執(zhí)行完同步代碼之后专缠,就會(huì)執(zhí)行位于執(zhí)行隊(duì)列和事件隊(duì)列之間的微任務(wù),然后再執(zhí)行事件隊(duì)列中的宏任務(wù)淑仆。

實(shí)例

new Promise(resolve => {
    resolve(1);
    
    Promise.resolve().then(() => {
        // t2
        console.log(2)
    });
    console.log(4)
}).then(t => {
    // t1
    console.log(t)
});
console.log(3);

這段代碼的流程大致如下:

  1. script 任務(wù)先運(yùn)行涝婉。首先遇到Promise實(shí)例,構(gòu)造函數(shù)首先執(zhí)行蔗怠,所以首先輸出了 4墩弯。此時(shí)microtask 的任務(wù)有 t2t1
  2. script 任務(wù)繼續(xù)運(yùn)行,輸出 3寞射。至此渔工,第一個(gè)宏任務(wù)執(zhí)行完成。
  3. 執(zhí)行所有的微任務(wù)桥温,先后取出 t2t1涨缚,分別輸出 21
  4. 代碼執(zhí)行完畢

綜上,上述代碼的輸出是:4321

事件循環(huán)

主線程從任務(wù)隊(duì)列中讀取事件策治,這個(gè)過(guò)程是循環(huán)不斷的脓魏,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))。

image

從上圖我們可以看出:

  • 主線程運(yùn)行的時(shí)候通惫,產(chǎn)生堆(heap)和棧(stack)茂翔。
  • 棧中的代碼調(diào)用各種外部API,它們?cè)?任務(wù)隊(duì)列"中加入各種事件(click履腋,load珊燎,done)。
  • 棧中的代碼執(zhí)行完畢遵湖,主線程就會(huì)去讀取任務(wù)隊(duì)列悔政,依次執(zhí)行那些事件所對(duì)應(yīng)的回調(diào)函數(shù)。

小結(jié)

事件循環(huán)其實(shí)并不難延旧,多查閱資料谋国,多看看相關(guān)例子就ok。希望一知半解的童鞋抓緊學(xué)習(xí)迁沫。

相關(guān)文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芦瘾,一起剝皮案震驚了整個(gè)濱河市捌蚊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌近弟,老刑警劉巖缅糟,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異祷愉,居然都是意外死亡窗宦,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門二鳄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)赴涵,“玉大人,你說(shuō)我怎么就攤上這事泥从。” “怎么了沪摄?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵躯嫉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我杨拐,道長(zhǎng)祈餐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任哄陶,我火速辦了婚禮帆阳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘屋吨。我一直安慰自己蜒谤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布至扰。 她就那樣靜靜地躺著鳍徽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪敢课。 梳的紋絲不亂的頭發(fā)上阶祭,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音直秆,去河邊找鬼濒募。 笑死,一個(gè)胖子當(dāng)著我的面吹牛圾结,可吹牛的內(nèi)容都是我干的瑰剃。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼筝野,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼培他!你這毒婦竟也來(lái)了鹃两?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舀凛,失蹤者是張志新(化名)和其女友劉穎俊扳,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猛遍,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馋记,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了懊烤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梯醒。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖腌紧,靈堂內(nèi)的尸體忽然破棺而出茸习,到底是詐尸還是另有隱情,我是刑警寧澤壁肋,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布号胚,位于F島的核電站,受9級(jí)特大地震影響浸遗,放射性物質(zhì)發(fā)生泄漏猫胁。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一跛锌、第九天 我趴在偏房一處隱蔽的房頂上張望弃秆。 院中可真熱鬧,春花似錦髓帽、人聲如沸菠赚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)锈至。三九已至,卻和暖如春译秦,著一層夾襖步出監(jiān)牢的瞬間峡捡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工筑悴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留们拙,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓阁吝,卻偏偏與公主長(zhǎng)得像砚婆,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

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