JS瀏覽器事件循環(huán)機(jī)制

文章來自我的 github 博客蹋偏,包括技術(shù)輸出和學(xué)習(xí)筆記扮匠,歡迎star莱坎。

先來明白些概念性內(nèi)容崔列。

進(jìn)程颅痊、線程

  • 進(jìn)程是系統(tǒng)分配的獨(dú)立資源膊毁,是 CPU 資源分配的基本單位耸棒,進(jìn)程是由一個(gè)或者多個(gè)線程組成的即寒。

  • 線程是進(jìn)程的執(zhí)行流惰帽,是CPU調(diào)度和分派的基本單位憨降,同個(gè)進(jìn)程之中的多個(gè)線程之間是共享該進(jìn)程的資源的。

瀏覽器內(nèi)核

  • 瀏覽器是多進(jìn)程的该酗,瀏覽器每一個(gè) tab 標(biāo)簽都代表一個(gè)獨(dú)立的進(jìn)程(也不一定授药,因?yàn)槎鄠€(gè)空白 tab 標(biāo)簽會(huì)合并成一個(gè)進(jìn)程),瀏覽器內(nèi)核(瀏覽器渲染進(jìn)程)屬于瀏覽器多進(jìn)程中的一種呜魄。

  • 瀏覽器內(nèi)核有多種線程在工作悔叽。

    • GUI 渲染線程:

      • 負(fù)責(zé)渲染頁面,解析 HTML爵嗅,CSS 構(gòu)成 DOM 樹等娇澎,當(dāng)頁面重繪或者由于某種操作引起回流都會(huì)調(diào)起該線程。
      • 和 JS 引擎線程是互斥的睹晒,當(dāng) JS 引擎線程在工作的時(shí)候趟庄,GUI 渲染線程會(huì)被掛起括细,GUI 更新被放入在 JS 任務(wù)隊(duì)列中,等待 JS 引擎線程空閑的時(shí)候繼續(xù)執(zhí)行戚啥。
    • JS 引擎線程:

      • 單線程工作奋单,負(fù)責(zé)解析運(yùn)行 JavaScript 腳本。
      • 和 GUI 渲染線程互斥猫十,JS 運(yùn)行耗時(shí)過長就會(huì)導(dǎo)致頁面阻塞览濒。
    • 事件觸發(fā)線程:

      • 當(dāng)事件符合觸發(fā)條件被觸發(fā)時(shí),該線程會(huì)把對(duì)應(yīng)的事件回調(diào)函數(shù)添加到任務(wù)隊(duì)列的隊(duì)尾拖云,等待 JS 引擎處理匾七。
    • 定時(shí)器觸發(fā)線程:

      • 瀏覽器定時(shí)計(jì)數(shù)器并不是由 JS 引擎計(jì)數(shù)的,阻塞會(huì)導(dǎo)致計(jì)時(shí)不準(zhǔn)確江兢。
      • 開啟定時(shí)器觸發(fā)線程來計(jì)時(shí)并觸發(fā)計(jì)時(shí)昨忆,計(jì)時(shí)完成后會(huì)被添加到任務(wù)隊(duì)列中,等待 JS 引擎處理杉允。
    • http 請(qǐng)求線程:

      • http 請(qǐng)求的時(shí)候會(huì)開啟一條請(qǐng)求線程邑贴。
      • 請(qǐng)求完成有結(jié)果了之后,將請(qǐng)求的回調(diào)函數(shù)添加到任務(wù)隊(duì)列中叔磷,等待 JS 引擎處理拢驾。
image

JavaScript 引擎是單線程

JavaScript 引擎是單線程,也就是說每次只能執(zhí)行一項(xiàng)任務(wù)改基,其他任務(wù)都得按照順序排隊(duì)等待被執(zhí)行繁疤,只有當(dāng)前的任務(wù)執(zhí)行完成之后才會(huì)往下執(zhí)行下一個(gè)任務(wù)。

HTML5 中提出了 Web-Worker API秕狰,主要是為了解決頁面阻塞問題稠腊,但是并沒有改變 JavaScript 是單線程的本質(zhì)。了解 Web-Worker鸣哀。

JavaScript 事件循環(huán)機(jī)制

JavaScript 事件循環(huán)機(jī)制分為瀏覽器和 Node 事件循環(huán)機(jī)制架忌,兩者的實(shí)現(xiàn)技術(shù)不一樣,瀏覽器 Event Loop 是 HTML 中定義的規(guī)范我衬,Node Event Loop 是由 libuv 庫實(shí)現(xiàn)叹放。這里主要講的是瀏覽器部分。

Javascript 有一個(gè) main thread 主線程和 call-stack 調(diào)用棧(執(zhí)行棧)挠羔,所有的任務(wù)都會(huì)被放到調(diào)用棧等待主線程執(zhí)行井仰。

  • JS 調(diào)用棧

    JS 調(diào)用棧是一種后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)。當(dāng)函數(shù)被調(diào)用時(shí)破加,會(huì)被添加到棧中的頂部俱恶,執(zhí)行完成之后就從棧頂部移出該函數(shù),直到棧內(nèi)被清空。

  • 同步任務(wù)速那、異步任務(wù)

    JavaScript 單線程中的任務(wù)分為同步任務(wù)和異步任務(wù)俐银。同步任務(wù)會(huì)在調(diào)用棧中按照順序排隊(duì)等待主線程執(zhí)行,異步任務(wù)則會(huì)在異步有了結(jié)果后將注冊(cè)的回調(diào)函數(shù)添加到任務(wù)隊(duì)列(消息隊(duì)列)中等待主線程空閑的時(shí)候端仰,也就是棧內(nèi)被清空的時(shí)候捶惜,被讀取到棧中等待主線程執(zhí)行。任務(wù)隊(duì)列是先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)荔烧。

  • Event Loop

    調(diào)用棧中的同步任務(wù)都執(zhí)行完畢吱七,棧內(nèi)被清空了,就代表主線程空閑了鹤竭,這個(gè)時(shí)候就會(huì)去任務(wù)隊(duì)列中按照順序讀取一個(gè)任務(wù)放入到棧中執(zhí)行踊餐。每次棧內(nèi)被清空,都會(huì)去讀取任務(wù)隊(duì)列有沒有任務(wù)臀稚,有就讀取執(zhí)行吝岭,一直循環(huán)讀取-執(zhí)行的操作,就形成了事件循環(huán)吧寺。

image
image
  • 定時(shí)器

    定時(shí)器會(huì)開啟一條定時(shí)器觸發(fā)線程來觸發(fā)計(jì)時(shí)窜管,定時(shí)器會(huì)在等待了指定的時(shí)間后將事件放入到任務(wù)隊(duì)列中等待讀取到主線程執(zhí)行。

    定時(shí)器指定的延時(shí)毫秒數(shù)其實(shí)并不準(zhǔn)確稚机,因?yàn)槎〞r(shí)器只是在到了指定的時(shí)間時(shí)將事件放入到任務(wù)隊(duì)列中幕帆,必須要等到同步的任務(wù)和現(xiàn)有的任務(wù)隊(duì)列中的事件全部執(zhí)行完成之后,才會(huì)去讀取定時(shí)器的事件到主線程執(zhí)行赖条,中間可能會(huì)存在耗時(shí)比較久的任務(wù)失乾,那么就不可能保證在指定的時(shí)間執(zhí)行。

  • 宏任務(wù)(macro-task)纬乍、微任務(wù)(micro-task)

    除了廣義的同步任務(wù)和異步任務(wù)碱茁,JavaScript 單線程中的任務(wù)可以細(xì)分為宏任務(wù)和微任務(wù)。

    macro-task包括:script(整體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering蕾额。

    micro-task包括:process.nextTick, Promises, Object.observe, MutationObserver早芭。

        console.log(1);
        setTimeout(function() {
            console.log(2);
        })
        var promise = new Promise(function(resolve, reject) {
            console.log(3);
            resolve();
        })
        promise.then(function() {
            console.log(4);
        })
        console.log(5);
    

    示例中彼城,setTimeout 和 Promise被稱為任務(wù)源诅蝶,來自不同的任務(wù)源注冊(cè)的回調(diào)函數(shù)會(huì)被放入到不同的任務(wù)隊(duì)列中。

    有了宏任務(wù)和微任務(wù)的概念后募壕,那 JS 的執(zhí)行順序是怎樣的调炬?是宏任務(wù)先還是微任務(wù)先?

    第一次事件循環(huán)中舱馅,JavaScript 引擎會(huì)把整個(gè) script 代碼當(dāng)成一個(gè)宏任務(wù)執(zhí)行缰泡,執(zhí)行完成之后,再檢測本次循環(huán)中是否尋在微任務(wù),存在的話就依次從微任務(wù)的任務(wù)隊(duì)列中讀取執(zhí)行完所有的微任務(wù)棘钞,再讀取宏任務(wù)的任務(wù)隊(duì)列中的任務(wù)執(zhí)行缠借,再執(zhí)行所有的微任務(wù),如此循環(huán)宜猜。JS 的執(zhí)行順序就是每次事件循環(huán)中的宏任務(wù)-微任務(wù)泼返。

    • 上面的示例中,第一次事件循環(huán)姨拥,整段代碼作為宏任務(wù)進(jìn)入主線程執(zhí)行绅喉。
    • 遇到了 setTimeout ,就會(huì)等到過了指定的時(shí)間后將回調(diào)函數(shù)放入到宏任務(wù)的任務(wù)隊(duì)列中叫乌。
    • 遇到 Promise柴罐,將 then 函數(shù)放入到微任務(wù)的任務(wù)隊(duì)列中。
    • 整個(gè)事件循環(huán)完成之后憨奸,會(huì)去檢測微任務(wù)的任務(wù)隊(duì)列中是否存在任務(wù)革屠,存在就執(zhí)行。
    • 第一次的循環(huán)結(jié)果打印為: 1,3,5,4排宰。
    • 接著再到宏任務(wù)的任務(wù)隊(duì)列中按順序取出一個(gè)宏任務(wù)到棧中讓主線程執(zhí)行屠阻,那么在這次循環(huán)中的宏任務(wù)就是 setTimeout 注冊(cè)的回調(diào)函數(shù),執(zhí)行完這個(gè)回調(diào)函數(shù)额各,發(fā)現(xiàn)在這次循環(huán)中并不存在微任務(wù)国觉,就準(zhǔn)備進(jìn)行下一次事件循環(huán)。
    • 檢測到宏任務(wù)隊(duì)列中已經(jīng)沒有了要執(zhí)行的任務(wù)虾啦,那么就結(jié)束事件循環(huán)麻诀。
    • 最終的結(jié)果就是 1,3,5,4,2。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末傲醉,一起剝皮案震驚了整個(gè)濱河市蝇闭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌硬毕,老刑警劉巖呻引,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異吐咳,居然都是意外死亡逻悠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門韭脊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來童谒,“玉大人,你說我怎么就攤上這事沪羔〖⒁粒” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長琅豆。 經(jīng)常有香客問我愉豺,道長,這世上最難降的妖魔是什么茫因? 我笑而不...
    開封第一講書人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任粒氧,我火速辦了婚禮,結(jié)果婚禮上节腐,老公的妹妹穿的比我還像新娘外盯。我一直安慰自己,他們只是感情好翼雀,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開白布饱苟。 她就那樣靜靜地躺著,像睡著了一般狼渊。 火紅的嫁衣襯著肌膚如雪箱熬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評(píng)論 1 299
  • 那天狈邑,我揣著相機(jī)與錄音城须,去河邊找鬼。 笑死米苹,一個(gè)胖子當(dāng)著我的面吹牛糕伐,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蘸嘶,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼良瞧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了训唱?” 一聲冷哼從身側(cè)響起褥蚯,我...
    開封第一講書人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎况增,沒想到半個(gè)月后赞庶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澳骤,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年歧强,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宴凉。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡誊锭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弥锄,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布籽暇,位于F島的核電站温治,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏戒悠。R本人自食惡果不足惜熬荆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望绸狐。 院中可真熱鬧卤恳,春花似錦、人聲如沸寒矿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽符相。三九已至拆融,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啊终,已是汗流浹背镜豹。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蓝牲,地道東北人趟脂。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像例衍,于是被迫代替她去往敵國和親散怖。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353