JS事件循環(huán)和宏任務(wù)微任務(wù)

一 進程與線程

進程和線程的概念用較為官方的術(shù)語描述來說是這樣的

1.進程是cpu資源分配的最小單位(是能擁有資源和獨立運行的最小單位)
2.線程是cpu調(diào)度的最小單位(線程是建立在進程的基礎(chǔ)上的一次程序運行單位,一個進程中可以有多個線程)

用LOL來比喻的話(舉例子可能不太貼切峦失,因為本人比較喜歡玩lol 所以用這個舉例子讓自己來加深印象)

一個進程就是一局游戲,每一局游戲都要有野怪小兵這些資源绿映;
每局游戲都相互獨立互不干涉;
一個線程就是一個英雄腐晾;
一局游戲里有多個英雄(一個進程有多個線程)叉弦;
英雄之間共享資源

映射關(guān)系為

一局游戲的野怪和小兵 > 系統(tǒng)分配的內(nèi)存
每一局游戲相互獨立 > 進程之間相互獨立
打團戰(zhàn) -> 多個線程在進程中協(xié)作完成任務(wù)
一局游戲可能一個玩也可能多個人玩>一個進程由一個或多個線程組成
英雄之間共享野怪小兵誰都可以吃 -> 同一進程下的各個線程之間共享程序的內(nèi)存空間(包括代碼段、數(shù)據(jù)集藻糖、堆等)

二 瀏覽器是多進程的

瀏覽器可以運行是因為系統(tǒng)給分配了資源和內(nèi)存淹冰,瀏覽器每一個tab頁都需要資源和內(nèi)存,所以每一個tab頁面都對應(yīng)至少一個進程颖御,在這里瀏覽器應(yīng)該也有自己的優(yōu)化機制榄棵,有時候打開多個tab頁后凝颇,可以在Chrome任務(wù)管理器中看到潘拱,有些進程被合并了 (所以每一個Tab標簽對應(yīng)一個進程并不一定是絕對的)
知道了瀏覽器是多進程后,再來看看它到底包含哪些進程:(為了簡化理解拧略,僅列舉主要進程)

1. Browser進程

瀏覽器的主進程(負責協(xié)調(diào)芦岂、主控),只有一個垫蛆。
負責瀏覽器界面顯示禽最,與用戶交互。如前進袱饭,后退等
負責各個頁面的管理川无,創(chuàng)建和銷毀其他進程
將Renderer進程得到的內(nèi)存中的Bitmap,繪制到用戶界面上
網(wǎng)絡(luò)資源的管理虑乖,下載等

2. 第三方插件進程

每種類型的插件對應(yīng)一個進程懦趋,僅當使用該插件時才創(chuàng)建 

3. GPU進程

最多一個,用于3D繪制等 

4. 瀏覽器渲染進程(瀏覽器內(nèi)核)(Renderer進程疹味,內(nèi)部是多線程的)

默認每個Tab頁面一個進程仅叫,互不影響帜篇。
主要作用為
頁面渲染,腳本執(zhí)行诫咱,事件處理等

三 瀏覽器渲染進程

作為前端開發(fā)工程師我們最主要的關(guān)注點還是渲染進程笙隙。
可以這樣理解,頁面的渲染坎缭,JS的執(zhí)行竟痰,事件的循環(huán),都在這個進程內(nèi)進行幻锁,渲染進程是多線程的
接下來重點分析這個進程的常駐線程凯亮。

1.GUI渲染進程

1.負責渲染瀏覽器界面,解析HTML哄尔,CSS假消,構(gòu)建DOM樹和RenderObject樹,布局和繪制等岭接。
2.當界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時富拗,該線程就會執(zhí)行。
3.注意鸣戴,GUI渲染線程與JS引擎線程是互斥的啃沪,當JS引擎執(zhí)行時GUI線程會被掛起(相當于被凍結(jié)了),GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執(zhí)行窄锅。

2.JS引擎線程

1.也稱為JS內(nèi)核创千,負責處理Javascript腳本程序。(例如V8引擎)
2.JS引擎線程負責解析Javascript腳本入偷,運行代碼追驴。
3.JS引擎一直等待著任務(wù)隊列中任務(wù)的到來,然后加以處理疏之,一個Tab頁(renderer進程)中無論什么時候都只有一個JS線程在運行JS程序
4.同樣注意殿雪,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時間過長锋爪,這樣就會造成頁面的渲染不連貫丙曙,導(dǎo)致頁面渲染加載阻塞。

3.事件處理線程

1.歸屬于瀏覽器而不是JS引擎其骄,用來控制事件循環(huán)(可以理解亏镰,JS引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助)
2.當JS引擎執(zhí)行代碼塊如setTimeOut時(也可來自瀏覽器內(nèi)核的其他線程,如鼠標點擊拯爽、AJAX異步請求等)索抓,會將對應(yīng)任務(wù)添加到事件線程中
3.當對應(yīng)的事件符合觸發(fā)條件被觸發(fā)時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理
4.注意纸兔,由于JS的單線程關(guān)系惰瓜,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執(zhí)行)

4.定時器線程

1.傳說中的setInterval與setTimeout所在線程
2.瀏覽器定時計數(shù)器并不是由JavaScript引擎計數(shù)的,(因為JavaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準確)
3.因此通過單獨線程來計時并觸發(fā)定時(計時完畢后,添加到事件隊列中汉矿,等待JS引擎空閑后執(zhí)行)
4.注意崎坊,W3C在HTML標準中規(guī)定,規(guī)定要求setTimeout中低于4ms的時間間隔算為4ms洲拇。

5.http異步請求線程

1.在XMLHttpRequest在連接后是通過瀏覽器新開一個線程請求

2.將檢測到狀態(tài)變更時奈揍,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件赋续,將這個回調(diào)再放入事件隊列中男翰。再由JavaScript引擎執(zhí)行。
image

四 事件循環(huán)

上文說了幾個線程


js引擎線程
事件觸發(fā)線程
定時觸發(fā)器線程

然后再理解一個概念:

JS分為同步任務(wù)和異步任務(wù)
1.同步任務(wù)都在主線程上執(zhí)行纽乱,形成一個執(zhí)行棧
2.主線程之外蛾绎,事件觸發(fā)線程管理著一個任務(wù)隊列,只要異步任務(wù)有了運行結(jié)果鸦列,就在任務(wù)隊列之中放置一個事件
3.一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢(此時JS引擎空閑)租冠,系統(tǒng)就會讀取任務(wù)隊列,將可運行的異步任務(wù)添加到可執(zhí)行棧中薯嗤,開始執(zhí)行
image

image
主線程運行時會產(chǎn)生執(zhí)行棧顽爹,棧中的代碼調(diào)用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發(fā)條件后骆姐,如ajax請求完畢)
而棧中的代碼執(zhí)行完畢镜粤,就會讀取事件隊列中的事件,去執(zhí)行那些回調(diào)
如此循環(huán)
注意玻褪,總是要等待棧中的代碼執(zhí)行完畢后才會去讀取事件隊列中的事件

五 宏任務(wù)微任務(wù)

上文中將JS事件循環(huán)機制梳理了一遍肉渴,在ES5的情況是夠用了,但是在ES6盛行的現(xiàn)在归园,仍然會遇到一些問題黄虱,譬如下面這題:

console.log('script start');
setTimeout(function() {
    console.log('setTimeout');
}, 0);
Promise.resolve().then(function() {
    console.log('promise1');
}).then(function() {
    console.log('promise2');
});
console.log('script end');

它的正確執(zhí)行順序是這樣子的:

script start
script end
promise1
promise2
setTimeout

為什么呢稚矿?因為Promise里有了一個一個新的概念:microtask庸诱。微任務(wù)
JS中分為兩種任務(wù)類型:macrotask和microtask,在ECMAScript中晤揣,microtask稱為jobs桥爽,macrotask可稱為task。
它們的定義昧识?區(qū)別钠四?簡單點可以按如下理解:

macrotask(又稱之為宏任務(wù)),可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個宏任務(wù)(包括每次從事件隊列中獲取一個事件回調(diào)并放到執(zhí)行棧中執(zhí)行)
每一個task會從頭到尾將這個任務(wù)執(zhí)行完畢,不會執(zhí)行其它
瀏覽器為了能夠使得JS內(nèi)部task與DOM任務(wù)能夠有序的執(zhí)行缀去,會在一個task執(zhí)行結(jié)束后侣灶,在下一個 task 執(zhí)行開始前,對頁面進行重新渲染
microtask(又稱為微任務(wù))缕碎,可以理解是在當前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
也就是說褥影,在當前task任務(wù)后,下一個task之前咏雌,在渲染之前
所以它的響應(yīng)速度相比setTimeout(setTimeout是task)會更快凡怎,因為無需等渲染
也就是說,在某一個macrotask執(zhí)行完后赊抖,就會將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)

分別很么樣的場景會形成macrotask和microtask呢统倒?

macrotask:主代碼塊,setTimeout氛雪,setInterval等(可以看到房匆,事件隊列中的每一個事件都是一個macrotask)
microtask:Promise,process.nextTick等

再根據(jù)線程來理解下:

macrotask中的事件都是放在一個事件隊列中的报亩,而這個隊列由事件觸發(fā)線程維護
microtask中的所有微任務(wù)都是添加到微任務(wù)隊列(Job Queues)中坛缕,等待當前macrotask執(zhí)行完畢后執(zhí)行,而這個隊列由JS引擎線程維護(這點由自己理解+推測得出捆昏,因為它是在主線程下無縫執(zhí)行的)

所以赚楚,總結(jié)下運行機制:

執(zhí)行一個宏任務(wù)(棧中沒有就從事件隊列中獲取)
執(zhí)行過程中如果遇到微任務(wù)骗卜,就將它添加到微任務(wù)的任務(wù)隊列中
宏任務(wù)執(zhí)行完畢后宠页,立即執(zhí)行當前微任務(wù)隊列中的所有微任務(wù)(依次執(zhí)行)
當前宏任務(wù)執(zhí)行完畢,開始檢查渲染寇仓,然后GUI線程接管渲染
渲染完畢后举户,JS線程繼續(xù)接管,開始下一個宏任務(wù)(從事件隊列中獲缺榉场)
image

參考文章
全面梳理JS引擎的運行機制
https://mp.weixin.qq.com/s?__biz=MzUzMjA3MTI2NQ==&mid=2247485214&idx=1&sn=12decb0ba6b6060d6b9f88ee6c227d7e&chksm=fab99110cdce1806c508950bf2cdb1b09c45fb9a48acbce868694ad980c112ddcee671b0a3f6&mpshare=1&scene=24&srcid=&sharer_sharetime=1585841105377&sharer_shareid=762717275fedf766441eee549354e3af&key=25482536a2868f95a747be1bba6a2a0f336defbbf8d45116a1dc006e3119777adad66c433cd6f4b685e308f086f164107dd3aa1c6b2eedb4d6e3007bab957877ea458e842beb61384aec253a8d3113a5&ascene=14&uin=MTc1MjM1NzM4MQ%3D%3D&devicetype=Windows+7&version=62080079&lang=zh_CN&exportkey=AewYYl61ye1z6y1NiRjiJEQ%3D&pass_ticket=C4is0r6nXq%2FvPIwtVRO8UIjTQ7xNdzevdyWz58B0vV70j5JGHCTivvcazNg4WduX](https://mp.weixin.qq.com/s?__biz=MzUzMjA3MTI2NQ==&mid=2247485214&idx=1&sn=12decb0ba6b6060d6b9f88ee6c227d7e&chksm=fab99110cdce1806c508950bf2cdb1b09c45fb9a48acbce868694ad980c112ddcee671b0a3f6&mpshare=1&scene=24&srcid=&sharer_sharetime=1585841105377&sharer_shareid=762717275fedf766441eee549354e3af&key=25482536a2868f95a747be1bba6a2a0f336defbbf8d45116a1dc006e3119777adad66c433cd6f4b685e308f086f164107dd3aa1c6b2eedb4d6e3007bab957877ea458e842beb61384aec253a8d3113a5&ascene=14&uin=MTc1MjM1NzM4MQ%3D%3D&devicetype=Windows+7&version=62080079&lang=zh_CN&exportkey=AewYYl61ye1z6y1NiRjiJEQ%3D&pass_ticket=C4is0r6nXq%2FvPIwtVRO8UIjTQ7xNdzevdyWz58B0vV70j5JGHCTivvcazNg4WduX

最后附上幾道題

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
  }
  async function async2() {
    console.log('async2');
  }
  console.log('script start');
  setTimeout(function() {
    console.log('setTimeout');
  }, 0)
  async1();
  new Promise(function(resolve) {
    console.log('promise1');
    resolve();
  }).then(function() {
    console.log('promise2');
  })
  console.log('script end');
console.log('1');
  setTimeout(function() {
    console.log('2');
    new Promise(function(resolve) {
      console.log('3');
      resolve();
    }).then(function() {
      console.log('4');
    })
  })
  new Promise(function(resolve) {
    console.log('5');
    resolve();
  }).then(function() {
    console.log('6');
  })
  setTimeout(function() {
    console.log('7');
  })
  setTimeout(function() {
    console.log('8');
    new Promise(function(resolve) {
      console.log('9');
      resolve();
    }).then(function() {
      console.log('10');
    })
  })
  new Promise(function(resolve) {
    console.log('11');
    resolve();
  }).then(function() {
    console.log('12');
  })
  console.log('13');
const promise = new Promise((resolve, reject) => {
    console.log("1");
    setTimeout(() => {
      console.log("2");
      setTimeout(() => {
        console.log('4');
      })
      resolve('success');
    }, 1000)
  })
  console.log('3');

  promise.then((res) => {
    return new Error('error!!!')
  }).then((res) => {
    console.log('then:', res)
  }).catch((err) => {
    console.log('catch:', err)
  })

  promise.then((res) => {
    console.log(res)
  }).catch((err) => {
    console.log('catch:', err);
  })

歡迎大家在評論區(qū)寫出執(zhí)行順序

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末俭嘁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子服猪,更是在濱河造成了極大的恐慌供填,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罢猪,死亡現(xiàn)場離奇詭異近她,居然都是意外死亡,警方通過查閱死者的電腦和手機膳帕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門粘捎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事攒磨∮捐耄” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵娩缰,是天一觀的道長蓬痒。 經(jīng)常有香客問我,道長漆羔,這世上最難降的妖魔是什么梧奢? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮演痒,結(jié)果婚禮上亲轨,老公的妹妹穿的比我還像新娘。我一直安慰自己鸟顺,他們只是感情好惦蚊,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著讯嫂,像睡著了一般蹦锋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上欧芽,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天莉掂,我揣著相機與錄音,去河邊找鬼千扔。 笑死憎妙,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的曲楚。 我是一名探鬼主播厘唾,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼龙誊!你這毒婦竟也來了抚垃?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤趟大,失蹤者是張志新(化名)和其女友劉穎鹤树,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體护昧,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡魂迄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年粗截,在試婚紗的時候發(fā)現(xiàn)自己被綠了惋耙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖绽榛,靈堂內(nèi)的尸體忽然破棺而出湿酸,到底是詐尸還是另有隱情,我是刑警寧澤灭美,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布推溃,位于F島的核電站,受9級特大地震影響届腐,放射性物質(zhì)發(fā)生泄漏铁坎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一犁苏、第九天 我趴在偏房一處隱蔽的房頂上張望硬萍。 院中可真熱鬧,春花似錦围详、人聲如沸朴乖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽买羞。三九已至,卻和暖如春雹食,著一層夾襖步出監(jiān)牢的瞬間畜普,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工群叶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留漠嵌,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓盖呼,卻偏偏與公主長得像儒鹿,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子几晤,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

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