亚洲中字慕日产2020,大陆极品少妇内射AAAAAA,无码av大香线蕉伊人久久,久久精品国产亚洲av麻豆网站

資訊專欄INFORMATION COLUMN

微任務(wù)、宏任務(wù)與Event-Loop

Nekron / 2397人閱讀

摘要:所以本來快輪到你來辦理業(yè)務(wù),會因為老大爺臨時添加的理財業(yè)務(wù)而往后推。在執(zhí)行完同步代碼與微任務(wù)以后,這時繼續(xù)向后查找有木有宏任務(wù)。所以輸出了第二次,等到這兩次都執(zhí)行完畢后才會去檢查有沒有微任務(wù)有沒有宏任務(wù)。

首先,JavaScript是一個單線程的腳本語言。
所以就是說在一行代碼執(zhí)行的過程中,必然不會存在同時執(zhí)行的另一行代碼,就像使用alert()以后進行瘋狂console.log,如果沒有關(guān)閉彈框,控制臺是不會顯示出一條log信息的。
亦或者有些代碼執(zhí)行了大量計算,比方說在前端暴力破解密碼之類的鬼操作,這就會導(dǎo)致后續(xù)代碼一直在等待,頁面處于假死狀態(tài),因為前邊的代碼并沒有執(zhí)行完。

所以如果全部代碼都是同步執(zhí)行的,這會引發(fā)很嚴(yán)重的問題,比方說我們要從遠(yuǎn)端獲取一些數(shù)據(jù),難道要一直循環(huán)代碼去判斷是否拿到了返回結(jié)果么?_就像去飯店點餐,肯定不能說點完了以后就去后廚催著人炒菜的,會被揍的。_
于是就有了異步事件的概念,注冊一個回調(diào)函數(shù),比如說發(fā)一個網(wǎng)絡(luò)請求,我們告訴主程序等到接收到數(shù)據(jù)后通知我,然后我們就可以去做其他的事情了。
然后在異步完成后,會通知到我們,但是此時可能程序正在做其他的事情,所以即使異步完成了也需要在一旁等待,等到程序空閑下來才有時間去看哪些異步已經(jīng)完成了,可以去執(zhí)行。
比如說打了個車,如果司機先到了,但是你手頭還有點兒事情要處理,這時司機是不可能自己先開著車走的,一定要等到你處理完事情上了車才能走。

微任務(wù)與宏任務(wù)的區(qū)別

這個就像去銀行辦業(yè)務(wù)一樣,先要取號進行排號。
一般上邊都會印著類似:“您的號碼為XX,前邊還有XX人?!敝惖淖謽?。

因為柜員同時職能處理一個來辦理業(yè)務(wù)的客戶,這時每一個來辦理業(yè)務(wù)的人就可以認(rèn)為是銀行柜員的一個宏任務(wù)來存在的,當(dāng)柜員處理完當(dāng)前客戶的問題以后,選擇接待下一位,廣播報號,也就是下一個宏任務(wù)的開始。
所以多個宏任務(wù)合在一起就可以認(rèn)為說有一個任務(wù)隊列在這,里邊是當(dāng)前銀行中所有排號的客戶。
任務(wù)隊列中的都是已經(jīng)完成的異步操作,而不是說注冊一個異步任務(wù)就會被放在這個任務(wù)隊列中,就像在銀行中排號,如果叫到你的時候你不在,那么你當(dāng)前的號牌就作廢了,柜員會選擇直接跳過進行下一個客戶的業(yè)務(wù)處理,等你回來以后還需要重新取號

而且一個宏任務(wù)在執(zhí)行的過程中,是可以添加一些微任務(wù)的,就像在柜臺辦理業(yè)務(wù),你前邊的一位老大爺可能在存款,在存款這個業(yè)務(wù)辦理完以后,柜員會問老大爺還有沒有其他需要辦理的業(yè)務(wù),這時老大爺想了一下:“最近P2P爆雷有點兒多,是不是要選擇穩(wěn)一些的理財呢”,然后告訴柜員說,要辦一些理財?shù)臉I(yè)務(wù),這時候柜員肯定不能告訴老大爺說:“您再上后邊取個號去,重新排隊”。
所以本來快輪到你來辦理業(yè)務(wù),會因為老大爺臨時添加的“理財業(yè)務(wù)”而往后推。
也許老大爺在辦完理財以后還想 再辦一個信用卡?或者 再買點兒紀(jì)念幣?
無論是什么需求,只要是柜員能夠幫她辦理的,都會在處理你的業(yè)務(wù)之前來做這些事情,這些都可以認(rèn)為是微任務(wù)。

這就說明:你大爺永遠(yuǎn)是你大爺
在當(dāng)前的微任務(wù)沒有執(zhí)行完成時,是不會執(zhí)行下一個宏任務(wù)的。

所以就有了那個經(jīng)常在面試題、各種博客中的代碼片段:

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
})

console.log(2)

setTimeout就是作為宏任務(wù)來存在的,而Promise.then則是具有代表性的微任務(wù),上述代碼的執(zhí)行順序就是按照序號來輸出的。

所有會進入的異步都是指的事件回調(diào)中的那部分代碼
也就是說new Promise在實例化的過程中所執(zhí)行的代碼都是同步進行的,而then中注冊的回調(diào)才是異步執(zhí)行的。
在同步代碼執(zhí)行完成后才回去檢查是否有異步任務(wù)完成,并執(zhí)行對應(yīng)的回調(diào),而微任務(wù)又會在宏任務(wù)之前執(zhí)行。
所以就得到了上述的輸出結(jié)論1、2、3、4。

+部分表示同步執(zhí)行的代碼

+setTimeout(_ => {
-  console.log(4)
+})

+new Promise(resolve => {
+  resolve()
+  console.log(1)
+}).then(_ => {
-  console.log(3)
+})

+console.log(2)

本來setTimeout已經(jīng)先設(shè)置了定時器(相當(dāng)于取號),然后在當(dāng)前進程中又添加了一些Promise的處理(臨時添加業(yè)務(wù))。

所以進階的,即便我們繼續(xù)在Promise中實例化Promise,其輸出依然會早于setTimeout的宏任務(wù):

setTimeout(_ => console.log(4))

new Promise(resolve => {
  resolve()
  console.log(1)
}).then(_ => {
  console.log(3)
  Promise.resolve().then(_ => {
    console.log("before timeout")
  }).then(_ => {
    Promise.resolve().then(_ => {
      console.log("also before timeout")
    })
  })
})

console.log(2)

當(dāng)然了,實際情況下很少會有簡單的這么調(diào)用Promise的,一般都會在里邊有其他的異步操作,比如fetchfs.readFile之類的操作。
而這些其實就相當(dāng)于注冊了一個宏任務(wù),而非是微任務(wù)。

P.S. 在Promise/A+的規(guī)范中,Promise的實現(xiàn)可以是微任務(wù),也可以是宏任務(wù),但是普遍的共識表示(至少Chrome是這么做的),Promise應(yīng)該是屬于微任務(wù)陣營的

所以,明白哪些操作是宏任務(wù)、哪些是微任務(wù)就變得很關(guān)鍵,這是目前業(yè)界比較流行的說法:

宏任務(wù)
# 瀏覽器 Node
I/O ? ?
setTimeout ? ?
setInterval ? ?
setImmediate ? ?
requestAnimationFrame ? ?

有些地方會列出來UI Rendering,說這個也是宏任務(wù),可是在讀了HTML規(guī)范文檔以后,發(fā)現(xiàn)這很顯然是和微任務(wù)平行的一個操作步驟
requestAnimationFrame姑且也算是宏任務(wù)吧,requestAnimationFrame在MDN的定義為,下次頁面重繪前所執(zhí)行的操作,而重繪也是作為宏任務(wù)的一個步驟來存在的,且該步驟晚于微任務(wù)的執(zhí)行

微任務(wù)
# 瀏覽器 Node
process.nextTick ? ?
MutationObserver ? ?
Promise.then catch finally ? ?
Event-Loop是個啥

上邊一直在討論 宏任務(wù)、微任務(wù),各種任務(wù)的執(zhí)行。
但是回到現(xiàn)實,JavaScript是一個單進程的語言,同一時間不能處理多個任務(wù),所以何時執(zhí)行宏任務(wù),何時執(zhí)行微任務(wù)?我們需要有這樣的一個判斷邏輯存在。

每辦理完一個業(yè)務(wù),柜員就會問當(dāng)前的客戶,是否還有其他需要辦理的業(yè)務(wù)。_(檢查還有沒有微任務(wù)需要處理)_
而客戶明確告知說沒有事情以后,柜員就去查看后邊還有沒有等著辦理業(yè)務(wù)的人。_(結(jié)束本次宏任務(wù)、檢查還有沒有宏任務(wù)需要處理)_
這個檢查的過程是持續(xù)進行的,每完成一個任務(wù)都會進行一次,而這樣的操作就被稱為Event Loop。_(這是個非常簡易的描述了,實際上會復(fù)雜很多)_

而且就如同上邊所說的,一個柜員同一時間只能處理一件事情,即便這些事情是一個客戶所提出的,所以可以認(rèn)為微任務(wù)也存在一個隊列,大致是這樣的一個邏輯:

const macroTaskList = [
  ["task1"],
  ["task2", "task3"],
  ["task4"],
]

for (let macroIndex = 0; macroIndex < macroTaskList.length; macroIndex++) {
  const microTaskList = macroTaskList[macroIndex]
  
  for (let microIndex = 0; microIndex < microTaskList.length; microIndex++) {
    const microTask = microTaskList[microIndex]

    // 添加一個微任務(wù)
    if (microIndex === 1) microTaskList.push("special micro task")
    
    // 執(zhí)行任務(wù)
    console.log(microTask)
  }

  // 添加一個宏任務(wù)
  if (macroIndex === 2) macroTaskList.push(["special macro task"])
}

// > task1
// > task2
// > task3
// > special micro task
// > task4
// > special macro task

之所以使用兩個for循環(huán)來表示,是因為在循環(huán)內(nèi)部可以很方便的進行push之類的操作(添加一些任務(wù)),從而使迭代的次數(shù)動態(tài)的增加。

以及還要明確的是,Event Loop只是負(fù)責(zé)告訴你該執(zhí)行那些任務(wù),或者說哪些回調(diào)被觸發(fā)了,真正的邏輯還是在進程中執(zhí)行的。

在瀏覽器中的表現(xiàn)

在上邊簡單的說明了兩種任務(wù)的差別,以及Event Loop的作用,那么在真實的瀏覽器中是什么表現(xiàn)呢?
首先要明確的一點是,宏任務(wù)必然是在微任務(wù)之后才執(zhí)行的(因為微任務(wù)實際上是宏任務(wù)的其中一個步驟)

I/O這一項感覺有點兒籠統(tǒng),有太多的東西都可以稱之為I/O,點擊一次button,上傳一個文件,與程序產(chǎn)生交互的這些都可以稱之為I/O。

假設(shè)有這樣的一些DOM結(jié)構(gòu):


const $inner = document.querySelector("#inner")
const $outer = document.querySelector("#outer")

function handler () {
  console.log("click") // 直接輸出

  Promise.resolve().then(_ => console.log("promise")) // 注冊微任務(wù)

  setTimeout(_ => console.log("timeout")) // 注冊宏任務(wù)

  requestAnimationFrame(_ => console.log("animationFrame")) // 注冊宏任務(wù)

  $outer.setAttribute("data-random", Math.random()) // DOM屬性修改,觸發(fā)微任務(wù)
}

new MutationObserver(_ => {
  console.log("observer")
}).observe($outer, {
  attributes: true
})

$inner.addEventListener("click", handler)
$outer.addEventListener("click", handler)

如果點擊#inner,其執(zhí)行順序一定是:click -> promise -> observer -> click -> promise -> observer -> animationFrame -> animationFrame -> timeout -> timeout。

因為一次I/O創(chuàng)建了一個宏任務(wù),也就是說在這次任務(wù)中會去觸發(fā)handler
按照代碼中的注釋,在同步的代碼已經(jīng)執(zhí)行完以后,這時就會去查看是否有微任務(wù)可以執(zhí)行,然后發(fā)現(xiàn)了PromiseMutationObserver兩個微任務(wù),遂執(zhí)行之。
因為click事件會冒泡,所以對應(yīng)的這次I/O會觸發(fā)兩次handler函數(shù)(_一次在inner、一次在outer_),所以會優(yōu)先執(zhí)行冒泡的事件(_早于其他的宏任務(wù)_),也就是說會重復(fù)上述的邏輯。
在執(zhí)行完同步代碼與微任務(wù)以后,這時繼續(xù)向后查找有木有宏任務(wù)。
需要注意的一點是,因為我們觸發(fā)了setAttribute,實際上修改了DOM的屬性,這會導(dǎo)致頁面的重繪,而這個set的操作是同步執(zhí)行的,也就是說requestAnimationFrame的回調(diào)會早于setTimeout所執(zhí)行。

一些小驚喜

使用上述的示例代碼,如果將手動點擊DOM元素的觸發(fā)方式變?yōu)?b>$inner.click(),那么會得到不一樣的結(jié)果。
Chrome下的輸出順序大致是這樣的:
click -> click -> promise -> observer -> promise -> animationFrame -> animationFrame -> timeout -> timeout。

與我們手動觸發(fā)click的執(zhí)行順序不一樣的原因是這樣的,因為并不是用戶通過點擊元素實現(xiàn)的觸發(fā)事件,而是類似dispatchEvent這樣的方式,我個人覺得并不能算是一個有效的I/O,在執(zhí)行了一次handler回調(diào)注冊了微任務(wù)、注冊了宏任務(wù)以后,實際上外邊的$inner.click()并沒有執(zhí)行完。
所以在微任務(wù)執(zhí)行之前,還要繼續(xù)冒泡執(zhí)行下一次事件,也就是說觸發(fā)了第二次的handler
所以輸出了第二次click,等到這兩次handler都執(zhí)行完畢后才會去檢查有沒有微任務(wù)、有沒有宏任務(wù)。

兩點需要注意的:

.click()的這種觸發(fā)事件的方式個人認(rèn)為是類似dispatchEvent,可以理解為同步執(zhí)行的代碼

document.body.addEventListener("click", _ => console.log("click"))

document.body.click()
document.body.dispatchEvent(new Event("click"))
console.log("done")

// > click
// > click
// > done

MutationObserver的監(jiān)聽不會說同時觸發(fā)多次,多次修改只會有一次回調(diào)被觸發(fā)。

new MutationObserver(_ => {
  console.log("observer")
  // 如果在這輸出DOM的data-random屬性,必然是最后一次的值,不解釋了
}).observe(document.body, {
  attributes: true
})

document.body.setAttribute("data-random", Math.random())
document.body.setAttribute("data-random", Math.random())
document.body.setAttribute("data-random", Math.random())

// 只會輸出一次 ovserver

這就像去飯店點餐,服務(wù)員喊了三次,XX號的牛肉面,不代表她會給你三碗牛肉面。
上述觀點參閱自Tasks, microtasks, queues and schedules,文中有動畫版的講解

在Node中的表現(xiàn)

Node也是單線程,但是在處理Event Loop上與瀏覽器稍微有些不同,這里是Node官方文檔的地址。

就單從API層面上來理解,Node新增了兩個方法可以用來使用:微任務(wù)的process.nextTick以及宏任務(wù)的setImmediate。

setImmediate與setTimeout的區(qū)別

在官方文檔中的定義,setImmediate為一次Event Loop執(zhí)行完畢后調(diào)用。
setTimeout則是通過計算一個延遲時間后進行執(zhí)行。

但是同時還提到了如果在主進程中直接執(zhí)行這兩個操作,很難保證哪個會先觸發(fā)。
因為如果主進程中先注冊了兩個任務(wù),然后執(zhí)行的代碼耗時超過XXs,而這時定時器已經(jīng)處于可執(zhí)行回調(diào)的狀態(tài)了。
所以會先執(zhí)行定時器,而執(zhí)行完定時器以后才是結(jié)束了一次Event Loop,這時才會執(zhí)行setImmediate

setTimeout(_ => console.log("setTimeout"))
setImmediate(_ => console.log("setImmediate"))

有興趣的可以自己試驗一下,執(zhí)行多次真的會得到不同的結(jié)果。

但是如果后續(xù)添加一些代碼以后,就可以保證setTimeout一定會在setImmediate之前觸發(fā)了:

setTimeout(_ => console.log("setTimeout"))
setImmediate(_ => console.log("setImmediate"))

let countdown = 1e9

while(countdown--) { } // 我們確保這個循環(huán)的執(zhí)行速度會超過定時器的倒計時,導(dǎo)致這輪循環(huán)沒有結(jié)束時,setTimeout已經(jīng)可以執(zhí)行回調(diào)了,所以會先執(zhí)行`setTimeout`再結(jié)束這一輪循環(huán),也就是說開始執(zhí)行`setImmediate`

如果在另一個宏任務(wù)中,必然是setImmediate先執(zhí)行:

require("fs").readFile(__dirname, _ => {
  setTimeout(_ => console.log("timeout"))
  setImmediate(_ => console.log("immediate"))
})

// 如果使用一個設(shè)置了延遲的setTimeout也可以實現(xiàn)相同的效果
process.nextTick

就像上邊說的,這個可以認(rèn)為是一個類似于PromiseMutationObserver的微任務(wù)實現(xiàn),在代碼執(zhí)行的過程中可以隨時插入nextTick,并且會保證在下一個宏任務(wù)開始之前所執(zhí)行。

在使用方面的一個最常見的例子就是一些事件綁定類的操作:

class Lib extends require("events").EventEmitter {
  constructor () {
    super()

    this.emit("init")
  }
}

const lib = new Lib()

lib.on("init", _ => {
  // 這里將永遠(yuǎn)不會執(zhí)行
  console.log("init!")
})

因為上述的代碼在實例化Lib對象時是同步執(zhí)行的,在實例化完成以后就立馬發(fā)送了init事件。
而這時在外層的主程序還沒有開始執(zhí)行到lib.on("init")監(jiān)聽事件的這一步。
所以會導(dǎo)致發(fā)送事件時沒有回調(diào),回調(diào)注冊后事件不會再次發(fā)送。

我們可以很輕松的使用process.nextTick來解決這個問題:

class Lib extends require("events").EventEmitter {
  constructor () {
    super()

    process.nextTick(_ => {
      this.emit("init")
    })

    // 同理使用其他的微任務(wù)
    // 比如Promise.resolve().then(_ => this.emit("init"))
    // 也可以實現(xiàn)相同的效果
  }
}

這樣會在主進程的代碼執(zhí)行完畢后,程序空閑時觸發(fā)Event Loop流程查找有沒有微任務(wù),然后再發(fā)送init事件。

關(guān)于有些文章中提到的,循環(huán)調(diào)用process.nextTick會導(dǎo)致報警,后續(xù)的代碼永遠(yuǎn)不會被執(zhí)行,這是對的,參見上邊使用的雙重循環(huán)實現(xiàn)的loop即可,相當(dāng)于在每次for循環(huán)執(zhí)行中都對數(shù)組進行了push操作,這樣循環(huán)永遠(yuǎn)也不會結(jié)束

多提一嘴async/await函數(shù)

因為,async/await本質(zhì)上還是基于Promise的一些封裝,而Promise是屬于微任務(wù)的一種。所以在使用await關(guān)鍵字與Promise.then效果類似:

setTimeout(_ => console.log(4))

async function main() {
  console.log(1)
  await Promise.resolve()
  console.log(3)
}

main()

console.log(2)

async函數(shù)在await之前的代碼都是同步執(zhí)行的,可以理解為await之前的代碼屬于new Promise時傳入的代碼,await之后的所有代碼都是在Promise.then中的回調(diào)

小節(jié)

JavaScript的代碼運行機制在網(wǎng)上有好多文章都寫,本人道行太淺,只能簡單的說一下自己對其的理解。
并沒有去生摳文檔,一步一步的列出來,像什么查看當(dāng)前棧、執(zhí)行選中的任務(wù)隊列,各種balabala。
感覺對實際寫代碼沒有太大幫助,不如簡單的入個門,掃個盲,大致了解一下這是個什么東西就好了。

推薦幾篇參閱的文章:

tasks-microtasks-queues-and-schedules

understanding-js-the-event-loop

理解Node.js里的process.nextTick()

瀏覽器中的EventLoop說明文檔

Node中的EventLoop說明文檔

requestAnimationFrame | MDN

MutationObserver | MDN

One more things

Blued前端/Node團隊招人。。初中高都有HC
坐標(biāo)帝都朝陽雙井,有興趣的請聯(lián)系我:
wechat: github_jiasm
mail: jiashunming@blued.com

歡迎砸簡歷

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/108240.html

相關(guān)文章

  • 徹底搞懂瀏覽器Event-loop

    摘要:檢查宏任務(wù)隊列,發(fā)現(xiàn)有的回調(diào)函數(shù)立即執(zhí)行回調(diào)函數(shù)輸出。接著遇到它的作用是在后將回調(diào)函數(shù)放到宏任務(wù)隊列中這個任務(wù)在再下一次的事件循環(huán)中執(zhí)行。 為什么會寫這篇博文呢? 前段時間,和頭條的小伙伴聊天問頭條面試前端會問哪些問題,他稱如果是他面試的話,event-loop肯定是要問的。那天聊了蠻多,event-loop算是給我留下了很深的印象,原因很簡單,因為之前我從未深入了解過,如果是面試的時...

    source 評論0 收藏0
  • JavaScript的event-loop

    摘要:從誕生之日起就是一門單線程的非阻塞的腳本語言。這意味著這些線程實際上應(yīng)屬于主線程的子線程。所以嚴(yán)格來講這些線程并沒有完整的功能,也因此這項技術(shù)并非改變了語言的單線程本質(zhì)。函數(shù)執(zhí)行棧和事件隊列 瀏覽器渲染 從耗時的角度,瀏覽器請求、加載、渲染一個頁面,時間花在下面五件事情上:1.DNS 查詢2.TCP 連接3.HTTP 請求即響應(yīng)4.服務(wù)器響應(yīng)5.客戶端渲染 這里重點討論第五個部分,即瀏...

    aboutU 評論0 收藏0
  • 結(jié)合microtask和macrotask理解event-loop

    摘要:講的很清晰,看完之后更深一步的理解了事件循環(huán)機制。簡短的概述下總結(jié)是一個宏任務(wù)源,寫在里面的回調(diào)函數(shù)會加到宏任務(wù)隊列中。至此,一輪的事件循環(huán)已經(jīng)執(zhí)行完畢,開啟新的一輪事件循環(huán)。這就是整段代碼執(zhí)行情況的理解。 這篇文章真的是好文。講的很清晰,看完之后更深一步的理解了事件循環(huán)機制。 http://www.jianshu.com/p/12b9... 簡短的概述下總結(jié) setTimeout是一...

    sarva 評論0 收藏0
  • 淺析 event-loop 事件輪詢

    摘要:如果執(zhí)行的準(zhǔn)備時間大于了,因為執(zhí)行同步代碼后,定時器的回調(diào)已經(jīng)被放入隊列,所以會先執(zhí)行隊列。 showImg(https://segmentfault.com/img/remote/1460000018998584); 閱讀原文 瀏覽器中的事件輪詢 JavaScript 是一門單線程語言,之所以說是單線程,是因為在瀏覽器中,如果是多線程,并且兩個線程同時操作了同一個 Dom 元素,...

    2501207950 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<