摘要:二瀏覽器端在講解事件循環(huán)之前先談?wù)勚型酱a異步代碼的執(zhí)行流程。三端我自己認(rèn)為的事件循環(huán)和瀏覽器端還是有點區(qū)別的,它的事件循環(huán)依靠引擎。四總結(jié)本篇主要介紹了瀏覽器和對于事件循環(huán)機制實現(xiàn),由于能力水平有限,其中可能有誤之處歡迎指出。
一、前言
前幾天聽公司一個公司三年的前端說“今天又學(xué)到了一個知識點-微任務(wù)、宏任務(wù)”,我問他這是什么東西,由于在吃飯他淺淺的說了下,當(dāng)時沒太理解就私下學(xué)習(xí)整理一番,由于談微任務(wù)、宏任務(wù)必談到事件循環(huán),于是就有了這篇博客。
在談到事件循環(huán)機制之前我們需要知道一些基礎(chǔ)知識就是:
js是單線程的
js一開始是作為腳本語言運行在客戶端
其實js是單線程在它作為腳本語言操作dom的時候就決定了。那么此時就有一個性能問題,那么js在瀏覽器端是如何處理這個問題的呢?同時,js在后臺Node中又是如何解決的呢?這就是本篇需要介紹的事件循環(huán)機制,這里我將分別以瀏覽器和Node兩個方面來分析。
二、瀏覽器端在講解事件循環(huán)之前先談?wù)刯s中同步代碼、異步代碼的執(zhí)行流程。
2.1、js同步代碼執(zhí)行過程js引擎在執(zhí)行通過代碼的過程中,會安裝順序依次存儲到一個地方去,這個地方就叫做執(zhí)行棧,當(dāng)我們調(diào)用一個方法的時候,js會生成一個和這個方法相對應(yīng)的上下文(context)。這個執(zhí)行環(huán)境中存在著這個方法的私有作用域,上層作用域的指向,方法的參數(shù),這個作用域中定義的變量以及這個作用域的this對象。
function a() { console.log("method a execute..."); } function b() { a(); } function c() { b(); } c();
以上面例子分析:js在執(zhí)行的時候會有一個全局上下文,我們這里就稱為GContext,下面分析步驟
調(diào)用c(),c入棧,此時棧中內(nèi)容為:GContext->c-contextC
接著調(diào)用b(),b入棧,此時棧中內(nèi)容為:GContext->c->contextC->b->contextB
接著調(diào)用a(),a入棧,此時棧中內(nèi)容為:GContext->c->contextC->b->contextB-c->contextC
a執(zhí)行完,a出棧,此時棧中內(nèi)容為:GContext->c->contextC->b->contextB
b執(zhí)行完,b出棧,此時棧中內(nèi)容為:GContext->c->contextC
c執(zhí)行完,b出棧,此時棧中內(nèi)容為:GContext
全部執(zhí)行完,釋放資源
ok,上面是同步代碼的執(zhí)行,上面會涉及到兩個核心概念:執(zhí)行整個代碼的線程我們稱之為主線程,存放方法執(zhí)行的地方我們稱之為執(zhí)行棧.
2.2、js異步代碼執(zhí)行過程上面說完了同步過程,那這里來談?wù)劗惒降倪^程。js引擎在遇到一個異步事件,不會一直等待返回結(jié)果而是將它掛起。當(dāng)異步任務(wù)執(zhí)行完之后會將結(jié)果加入到和執(zhí)行棧中不同的任務(wù)隊列當(dāng)中,注意的是:此時放入隊列不會立即執(zhí)行其回調(diào),而是當(dāng)主線程執(zhí)行完執(zhí)行棧中所有的任務(wù)之后再去隊列中查找是否有任務(wù),如果有則取出排在第一位的事件然后將回調(diào)放入執(zhí)行棧并執(zhí)行其代碼。如此反復(fù)就構(gòu)成了事件循環(huán)。
這里同樣有一個核心概念:任務(wù)隊列
2.3、微任務(wù)、宏任務(wù)上面提到j(luò)s執(zhí)行異步方法的時候會將其返回結(jié)果放到隊列中,這是比較籠統(tǒng)的,具體來說,js會根據(jù)任務(wù)的類型將其放入不同的隊列,任務(wù)類型有兩種:微任務(wù)、宏任務(wù)。那么其對應(yīng)的哪些是微任務(wù)、哪些是宏任務(wù)呢?
微任務(wù):Promise、process.nextTick()、整體代碼script、Object.observer、MutationObserver
宏任務(wù):setTimeout()、setInterval()
瀏覽器在執(zhí)行的時候,先從宏任務(wù)隊列中取出一個宏任務(wù)執(zhí)行宏,然后在執(zhí)行該宏任務(wù)下的所有的微任務(wù),這是一個循環(huán);然后再取出并執(zhí)行下一個宏任務(wù),再執(zhí)行所有的微任務(wù),這是第二個循環(huán),以此類推.
注意:整個javascript代碼是第一個宏任務(wù)
const process = require("process") setTimeout(function () {// 分發(fā)宏任務(wù)到EventQueue console.log("1"); }, 0); setTimeout(() => { console.log("11"); }, 0); setTimeout(() => { console.log("111"); }, 0); new Promise(function (resolve) { console.log("2"); resolve(); }).then(function () {// 發(fā)送微任務(wù) console.log("3"); });
// 輸出 2 3 1 11 1112.4、小結(jié)
在瀏覽器端,在我們執(zhí)行一片script的時候,當(dāng)遇到同步代碼,依次進(jìn)入執(zhí)行棧,遇到異步代碼,將其掛起,繼續(xù)執(zhí)行其它方法,當(dāng)異步方法執(zhí)行完之后根據(jù)任務(wù)類型進(jìn)入到任務(wù)隊列,在執(zhí)行棧執(zhí)行完,主線程空閑下來了之后會到任務(wù)隊列中取任務(wù)回調(diào)并執(zhí)行。
三、Node端我自己認(rèn)為Node的事件循環(huán)和瀏覽器端還是有點區(qū)別的,它的事件循環(huán)依靠libuv引擎。
該圖來自官網(wǎng),這里展示了在node的事件循環(huán)的6個階段。
timers:該階段執(zhí)行定時器的回調(diào),如setTimeout() 和 setInterval()。
I/O callbacks:該階段執(zhí)行除了close事件,定時器和setImmediate()的回調(diào)外的所有回調(diào)
idle, prepare:內(nèi)部使用
poll:等待新的I/O事件,node在一些特殊情況下會阻塞在這里
check: setImmediate()的回調(diào)會在這個階段執(zhí)行
close callbacks: 例如socket.on("close", ...)這種close事件的回調(diào)
對于我們來說我們更關(guān)注 timer、poll、check這三個階段即可。
poll 階段有兩個主要的功能:
處理poll隊列(poll quenue)的事件(callback);
執(zhí)行timers的callback,當(dāng)?shù)竭_(dá)timers指定的時間時;
poll 階段的邏輯
如果event loop進(jìn)入了 poll階段,且代碼未設(shè)定timer,將會發(fā)生下面情況:
a、如果poll queue不為空,event loop將同步的執(zhí)行queue里的callback,直至queue為空,或執(zhí)行的callback到達(dá)系統(tǒng)上限;
b、如果poll queue為空,將會發(fā)生下面情況:
* 如果代碼已經(jīng)被setImmediate()設(shè)定了callback, event loop將結(jié)束poll階段進(jìn)入check階段,并執(zhí)行check階段的queue (check階段的queue是 setImmediate設(shè)定的) * 如果代碼沒有設(shè)定setImmediate(callback),event loop將阻塞在該階段等待callbacks加入poll queue;
如果event loop進(jìn)入了 poll階段,且代碼設(shè)定了timer:
如果poll queue進(jìn)入空狀態(tài)時(即poll 階段為空閑狀態(tài)),event loop將檢查timers,
如果有1個或多個timers時間時間已經(jīng)到達(dá),event loop將按循環(huán)順序進(jìn)入 timers 階段,并執(zhí)行timer queue
3.1、setTimeout、setImmediate這兩個函數(shù)的功能還是類似的,不同的是他們處于EventLoop的不同階段:timer、check。
setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")},0);
上面兩行代碼會輸出順序是什么呢?其實兩種可能都有.
1.當(dāng)setTimeout的0ms并不能做到絕對0ms,如果已經(jīng)過了timer階段,那么此時setTimeout就會在下一次循環(huán)中執(zhí)行,也就是說先setInterval、再setTimeout。
2.第二種可能就是正常流程了,先timer、再check
如果上面的代碼再一個IO操作作呢?如:
require("fs").readFile(__filename,()=>{ setImmediate(()=>console.log("setInterval")); setTimeout(() => {console.log("setTimeout")}); })
此時只可能出現(xiàn)一種情況,先setInterval、再setTimeout,因為在io中已經(jīng)執(zhí)行過了timer(readFile時處于IO callback)。
下面一起來看如下代碼:
setTimeout(() => { console.log("timer1") Promise.resolve().then(() => console.log("promise1")); process.nextTick(() => console.log("nextTick1")) }, 0); setTimeout(() => { console.log("timer2") Promise.resolve().then(() => console.log("promise2")); process.nextTick(() => console.log("nextTick2")) }, 0);
按照我的理解,它的輸出應(yīng)該是如下:先timer、然后切換階段的時候執(zhí)行微任務(wù).
// 情況1 timer1 timer2 nextTick1 nextTick2 promise1 promise2
可是并不是,它的輸出一直是:
// 情況2 timer1 nextTick1 promise1 timer2 nextTick2 promise2
后臺晚上查資料因為Node11對EventLoop作了修改,為了和瀏覽器兼容。于是呼我切換到10.8.0,發(fā)現(xiàn)上面兩種情況都有(情況1比例大于情況2)。這點暫時還未查明什么原因。
3.2、小結(jié)node中的6個階段每個階段執(zhí)行完都會伴隨著執(zhí)行微任務(wù),同個MicroTask隊列下process.tick()會優(yōu)于Promise。
四 總結(jié)本篇主要介紹了瀏覽器和Node對于事件循環(huán)機制實現(xiàn),由于能力水平有限,其中可能有誤之處歡迎指出。
歡迎關(guān)注公眾號:
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/109612.html
摘要:的單線程,與它的用途有關(guān)。特點的顯著特點異步機制事件驅(qū)動。隊列的讀取輪詢線程,事件的消費者,的主角。它將不同的任務(wù)分配給不同的線程,形成一個事件循環(huán),以異步的方式將任務(wù)的執(zhí)行結(jié)果返回給引擎。 這兩天跟同事同事討論遇到的一個問題,js中的event loop,引出了chrome與node中運行具有setTimeout和Promise的程序時候執(zhí)行結(jié)果不一樣的問題,從而引出了Nodejs的...
摘要:單線程的話,如果我們做一些的操作比如說這是一個耗時的操所那么在這將近一秒內(nèi),線程就會被阻塞,無法繼續(xù)執(zhí)行下面的任務(wù)。事件循環(huán)的主要機制就是任務(wù)隊列機制一個事件循環(huán)有一個或者多個任務(wù)隊列。 瀏覽器中的事件循環(huán)機制 網(wǎng)上一搜事件循環(huán), 很多文章標(biāo)題的前面會加上 JavaScript, 但是我覺得事件循環(huán)機制跟 JavaScript 沒什么關(guān)系, JavaScript 只是一門解釋型語言, ...
摘要:如果當(dāng)前沒有事件也沒有定時器事件,則返回。相關(guān)資料關(guān)于的架構(gòu)及設(shè)計思路的事件討論了使用線程池異步運行代碼。下一篇初窺事件機制的實現(xiàn)二中定時器的實現(xiàn) 在瀏覽器中,事件作為一個極為重要的機制,給予JavaScript響應(yīng)用戶操作與DOM變化的能力;在Node.js中,事件驅(qū)動模型則是其高并發(fā)能力的基礎(chǔ)。 學(xué)習(xí)JavaScript也需要了解它的運行平臺,為了更好的理解JavaScript的事...
摘要:的事件循環(huán)一個線程有唯一的一個事件循環(huán)。索引就是指否還有需要執(zhí)行的事件,是否還有請求,關(guān)閉事件循環(huán)的請求等等。先來看一下定義的定義是在事件循環(huán)的下一個階段之前執(zhí)行對應(yīng)的回調(diào)。雖然是這樣定義的,但是它并不是為了在事件循環(huán)的每個階段去執(zhí)行的。 Node中的事件循環(huán) 如果對前端瀏覽器的時間循環(huán)不太清楚,請看這篇文章。那么node中的事件循環(huán)是什么樣子呢?其實官方文檔有很清楚的解釋,本文先從n...
摘要:瀏覽器中與中事件循環(huán)與執(zhí)行機制不同,不可混為一談。瀏覽器環(huán)境執(zhí)行為單線程不考慮,所有代碼皆在執(zhí)行線程調(diào)用棧完成執(zhí)行。參考文章強烈推薦不要混淆和瀏覽器中的強烈推薦中的模塊強烈推薦理解事件循環(huán)一淺析定時器詳解 注意 在 node 11 版本中,node 下 Event Loop 已經(jīng)與瀏覽器趨于相同。在 node 11 版本中,node 下 Event Loop 已經(jīng)與瀏覽器趨于相同。在 ...
閱讀 2302·2023-04-25 16:42
閱讀 1204·2021-11-22 14:45
閱讀 2341·2021-10-19 13:10
閱讀 2828·2021-09-29 09:34
閱讀 3412·2021-09-23 11:21
閱讀 2103·2021-08-12 13:25
閱讀 2185·2021-07-30 15:15
閱讀 3496·2019-08-30 15:54