自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

熟悉事件循環(huán)?為什么會分為宏任務和微任務

開發(fā) 前端
事件循環(huán)中的任務被分為宏任務和微任務,是為了給高優(yōu)先級任務一個插隊的機會:微任務比宏任務有更高優(yōu)先級。

什么是事件循環(huán)

在了解事件循環(huán)前,需要一些有關 JS 特性的前置知識。

JS 引擎是單線程的,直白來說就是一個時間點下 JS 引擎只能去做一件事情,而 Java 這種多線程語言,可以同時做幾件事情。

JS 做的任務分為同步和異步兩種,所謂 "異步",簡單說就是一個任務不是連續(xù)完成的,先執(zhí)行第一段,等做好了準備,再回過頭執(zhí)行第二段,第二段也被叫做回調;同步則是連貫完成的。

像讀取文件、網絡請求這種任務屬于異步任務:花費時間很長,但中間的操作不需要 JS 引擎自己完成,它只用等別人準備好了,把數(shù)據(jù)給他,他再繼續(xù)執(zhí)行回調部分。

如果沒有特殊處理,JS 引擎在執(zhí)行異步任務時,應該是存在等待的,不去做任何其他事情。用一個圖來展示這個過程,可以看出,在執(zhí)行異步任務時有大量的空閑時間被浪費。

實際上這是大多數(shù)多線程語言的處理辦法。但對于 JS 這種單線程語言來說,這種長時間的空閑等待是不可接受的:遇到其他緊急任務,Java 可以再開一個線程去處理,JS 卻只能忙等。

所以采取了以下的“異步任務回調通知”模式:

在等待異步任務準備的同時,JS 引擎去執(zhí)行其他同步任務,等到異步任務準備好了,再去執(zhí)行回調。這種模式的優(yōu)勢顯而易見,完成相同的任務,花費的時間大大減少,這種方式也被叫做非阻塞式。

而實現(xiàn)這個“通知”的,正是事件循環(huán),把異步任務的回調部分交給事件循環(huán),等時機合適交還給 JS 線程執(zhí)行。事件循環(huán)并不是 JavaScript 首創(chuàng)的,它是計算機的一種運行機制。

事件循環(huán)是由一個隊列組成的,異步任務的回調遵循先進先出,在 JS 引擎空閑時會一輪一輪地被取出,所以被叫做循環(huán)。

根據(jù)隊列中任務的不同,分為宏任務和微任務。

宏任務和微任務

事件循環(huán)由宏任務和在執(zhí)行宏任務期間產生的所有微任務組成。完成當下的宏任務后,會立刻執(zhí)行所有在此期間入隊的微任務。

這種設計是為了給緊急任務一個插隊的機會,否則新入隊的任務永遠被放在隊尾。區(qū)分了微任務和宏任務后,本輪循環(huán)中的微任務實際上就是在插隊,這樣微任務中所做的狀態(tài)修改,在下一輪事件循環(huán)中也能得到同步。

常見的宏任務有:script(整體代碼)/setTimout/setInterval/setImmediate(node 獨有)/requestAnimationFrame(瀏覽器獨有)/IO/UI render(瀏覽器獨有)

常見的微任務有:process.nextTick(node 獨有)/Promise.then()/Object.observe/MutationObserver

宏任務 setTimeout 的誤區(qū)

setTimeout 的回調不一定在指定時間后能執(zhí)行。而是在指定時間后,將回調函數(shù)放入事件循環(huán)的隊列中。

如果時間到了,JS 引擎還在執(zhí)行同步任務,這個回調函數(shù)需要等待;如果當前事件循環(huán)的隊列里還有其他回調,需要等其他回調執(zhí)行完。

另外,setTimeout 0ms 也不是立刻執(zhí)行,它有一個默認最小時間,為 4ms。所以下面這段代碼的輸出結果不一定:

// node
setTimeout(() => {
console.log('setTimeout')
}, 0)
setImmediate(() => {
console.log('setImmediate')
})

因為取出第一個宏任務之前在執(zhí)行全局 Script,如果這個時間大于 4ms,這時 setTimeout 的回調函數(shù)已經放入隊列,就先執(zhí)行 setTimeout;如果準備時間小于 4ms,就會先執(zhí)行 setImmediate。

瀏覽器的事件循環(huán)

瀏覽器的事件循環(huán)由一個宏任務隊列+多個微任務隊列組成。

首先,執(zhí)行第一個宏任務:全局 Script 腳本。產生的的宏任務和微任務進入各自的隊列中。執(zhí)行完 Script 后,把當前的微任務隊列清空。完成一次事件循環(huán)。

接著再取出一個宏任務,同樣把在此期間產生的回調入隊。再把當前的微任務隊列清空。以此往復。

宏任務隊列只有一個,而每一個宏任務都有一個自己的微任務隊列,每輪循環(huán)都是由一個宏任務+多個微任務組成。

下面的 Demo 展示了微任務的插隊過程:

Promise.resolve().then(()=>{
console.log('第一個回調函數(shù):微任務1')
setTimeout(()=>{
console.log('第三個回調函數(shù):宏任務2')
},0)
})
setTimeout(()=>{
console.log('第二個回調函數(shù):宏任務1')
Promise.resolve().then(()=>{
console.log('第四個回調函數(shù):微任務2')
})
},0)
// 第一個回調函數(shù):微任務1
// 第二個回調函數(shù):宏任務1
// 第四個回調函數(shù):微任務2
// 第三個回調函數(shù):宏任務2

打印的結果不是從 1 到 4,而是先執(zhí)行第四個回調函數(shù),再執(zhí)行第三個,因為它是一個微任務,比第三個回調函數(shù)有更高優(yōu)先級。

Node 的事件循環(huán)

node 的事件循環(huán)比瀏覽器復雜很多。由 6 個宏任務隊列+6 個微任務隊列組成。

宏任務按照優(yōu)先級從高到低依次是:

其執(zhí)行規(guī)律是:在一個宏任務隊列全部執(zhí)行完畢后,去清空一次微任務隊列,然后到下一個等級的宏任務隊列,以此往復。

一個宏任務隊列搭配一個微任務隊列。六個等級的宏任務全部執(zhí)行完成,才是一輪循環(huán)。

其中需要關注的是:Timers、Poll、Check 階段,因為我們所寫的代碼大多屬于這三個階段。

  1. Timers:定時器 setTimeout/setInterval;
  2. Poll :獲取新的 I/O 事件, 例如操作讀取文件等;
  3. Check:setImmediate 回調函數(shù)在這里執(zhí)行;

除此之外,node 端微任務也有優(yōu)先級先后:

  1. process.nextTick;
  2. promise.then 等;

清空微任務隊列時,會先執(zhí)行 process.nextTick,然后才是微任務隊列中的其他。下面這段代碼可以佐證瀏覽器和 node 的差異:

console.log('Script開始')
setTimeout(() => {
console.log('第一個回調函數(shù),宏任務1')
Promise.resolve().then(function() {
console.log('第四個回調函數(shù),微任務2')
})
}, 0)
setTimeout(() => {
console.log('第二個回調函數(shù),宏任務2')
Promise.resolve().then(function() {
console.log('第五個回調函數(shù),微任務3')
})
}, 0)
Promise.resolve().then(function() {
console.log('第三個回調函數(shù),微任務1')
})
console.log('Script結束')
node端:
Script開始
Script結束
第三個回調函數(shù),微任務1
第一個回調函數(shù),宏任務1
第二個回調函數(shù),宏任務2
第四個回調函數(shù),微任務2
第五個回調函數(shù),微任務3
瀏覽器
Script開始
Script結束
第三個回調函數(shù),微任務1
第一個回調函數(shù),宏任務1
第四個回調函數(shù),微任務2
第二個回調函數(shù),宏任務2
第五個回調函數(shù),微任務3

可以看出,在 node 端要等當前等級的所有宏任務完成,才能輪到微任務:第四個回調函數(shù),微任務2在兩個 setTimeout 完成后才打印。

因為瀏覽器執(zhí)行時是一個宏任務+一個微任務隊列,而 node 是一整個宏任務隊列+一個微任務隊列。

node11.x 前后版本差異

node11.x 之前,其事件循環(huán)的規(guī)則就如上文所述:先取出完一整個宏任務隊列中全部任務,然后執(zhí)行一個微任務隊列。

但在 11.x 之后,node 端的事件循環(huán)變得和瀏覽器類似:先執(zhí)行一個宏任務,然后是一個微任務隊列。但依然保留了宏任務隊列和微任務隊列的優(yōu)先級。可以用下面的 Demo 佐證:

console.log('Script開始')
setTimeout(() => {
console.log('宏任務1(setTimeout)')
Promise.resolve().then(() => {
console.log('微任務promise2')
})
}, 0)
setImmediate(() => {
console.log('宏任務2')
})
setTimeout(() => {
console.log('宏任務3(setTimeout)')
}, 0)
console.log('Script結束')
Promise.resolve().then(() => {
console.log('微任務promise1')
})
process.nextTick(() => {
console.log('微任務nextTick')
})

在 node11.x 之前運行:

Script開始
Script結束
微任務nextTick
微任務promise1
宏任務1(setTimeout)
宏任務3(setTimeout)
微任務promise2
宏任務2(setImmediate)

在 node11.x 之后運行:

Script開始
Script結束
微任務nextTick
微任務promise1
宏任務1(setTimeout)
微任務promise2
宏任務3(setTimeout)
宏任務2(setImmediate)

可以發(fā)現(xiàn),在不同的 node 環(huán)境下:

  1. 微任務隊列中 process.nextTick 都有更高優(yōu)先級,即使它后進入微任務隊列,也會先打印微任務nextTick再微任務promise1;
  2. 宏任務 setTimeout 比 setImmediate 優(yōu)先級更高,宏任務2(setImmediate)是三個宏任務中最后打印的;
  3. 在 node11.x 之前,微任務隊列要等當前優(yōu)先級的所有宏任務先執(zhí)行完,在兩個 setTimeout 之后才打印微任務promise2;在 node11.x 之后,微任務隊列只用等當前這一個宏任務先執(zhí)行完。

結語

事件循環(huán)中的任務被分為宏任務和微任務,是為了給高優(yōu)先級任務一個插隊的機會:微任務比宏任務有更高優(yōu)先級。

node 端的事件循環(huán)比瀏覽器更復雜,它的宏任務分為六個優(yōu)先級,微任務分為兩個優(yōu)先級。node 端的執(zhí)行規(guī)律是一個宏任務隊列搭配一個微任務隊列,而瀏覽器是一個單獨的宏任務搭配一個微任務隊列。但是在 node11 之后,node 和瀏覽器的規(guī)律趨同。

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2020-12-29 08:21:03

JavaScript微任務宏任務

2021-07-24 11:15:19

開發(fā)技能代碼

2021-01-18 08:24:51

JavaScriptMicrotask微任務

2023-04-06 00:22:19

JavaScrip任務開發(fā)

2009-11-17 10:31:12

Solaris項目管理任務管理

2021-08-03 07:40:47

宏任務微任務React

2018-09-19 10:18:34

行式存儲列式存儲數(shù)據(jù)庫

2017-05-02 22:38:44

前端開發(fā)JS事件循環(huán)機制

2023-11-13 07:37:36

JS面試題線程

2021-07-18 09:15:30

數(shù)據(jù)中心

2019-05-14 13:42:16

物聯(lián)網安全物聯(lián)網IOT

2019-07-30 15:50:00

2023-05-08 16:38:46

任務調度分布式任務調度

2025-02-24 09:00:00

CPUI/O密集型任務

2022-06-20 07:44:34

ahooks定時器

2021-12-04 22:05:41

網頁任務 Performanc

2023-08-11 16:28:24

2019-11-14 10:00:18

Linuxcron任務自動化任務

2022-06-18 23:10:56

前端模塊循環(huán)依賴

2021-12-25 22:29:57

Node.js 微任務處理事件循環(huán)
點贊
收藏

51CTO技術棧公眾號