日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪(fǎng)問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 前端技术 > HTML >内容正文

HTML

浏览器事件循环Event Loop

發(fā)布時(shí)間:2023/11/16 HTML 72 coder
生活随笔 收集整理的這篇文章主要介紹了 浏览器事件循环Event Loop 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

引言:

事件循環(huán)不是瀏覽器獨(dú)有的,從字面上看,“循環(huán)”可以簡(jiǎn)單地認(rèn)為就是重復(fù),比如for循環(huán),就是重復(fù)地執(zhí)行for循環(huán)體中的語(yǔ)句,所以事件循環(huán),可以理解為重復(fù)地處理事件,那么下一個(gè)問(wèn)題是,處理的是什么事件,事件的相關(guān)信息從哪里獲取。

因?yàn)槲覜](méi)有用nodejs做過(guò)什么項(xiàng)目,所以這里我暫且只關(guān)注瀏覽器的事件循環(huán),但我想就“事件循環(huán)”本身而言,原理應(yīng)該是相同的,不過(guò)就具體的實(shí)現(xiàn)可能存在一些差異。

一道面試題

相信應(yīng)該有部分小伙伴和我一樣,在面試中曾遇到過(guò)類(lèi)似于這種問(wèn)打印結(jié)果的題目。

(async function main() {
  console.log(1);

  setTimeout(() => {
    console.log(2);
  }, 0);

  setTimeout(() => {
    console.log(3);
  }, 100);

  let p1 = new Promise((resolve, reject) => {
    console.log(4);

    resolve(5);
    console.log(6);
  });

  p1.then((res) => {
    console.log(res);
  });

  let result = await Promise.resolve(7);
  console.log(result);

  console.log(8);
})()

這種題目就是變相的在考察事件循環(huán)的知識(shí)。

我個(gè)人感覺(jué)事件循環(huán)這個(gè)點(diǎn),也是隨著Promise的出現(xiàn),成為了一個(gè)常見(jiàn)的考點(diǎn)。

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

一提到事件循環(huán),我想很多人會(huì)和我一樣,立刻想到異步、宏任務(wù)、微任務(wù)什么的。

WIKI

先不著急,我們先看下Wiki上,對(duì)事件循環(huán)的通用性描述。

In computer science, the event loop is a programming construct or design pattern that waits for and dispatches events or messages in a program. The event loop works by making a request to some internal or external "event provider" (that generally blocks the request until an event has arrived), then calls the relevant event handler ("dispatches the event"). The event loop is also sometimes referred to as the message dispatcher, message loop, message pump, or run loop.

The event-loop may be used in conjunction with a reactor, if the event provider follows the file interface, which can be selected or 'polled' (the Unix system call, not actual polling). The event loop almost always operates asynchronously with the message originator.

When the event loop forms the central control flow construct of a program, as it often does, it may be termed the main loop or main event loop. This title is appropriate, because such an event loop is at the highest level of control within the program.

簡(jiǎn)而言之,事件循環(huán)是一種編程結(jié)構(gòu)或設(shè)計(jì)模式,用于在程序中等待和派發(fā)事件或消息。

它的工作原理是,向內(nèi)部或外部的“事件提供者”發(fā)出請(qǐng)求(通常會(huì)阻止請(qǐng)求,直到事件發(fā)生)這就回答了我們之前的問(wèn)題:事件的信息從哪里來(lái),是由“事件提供者”提供,然后調(diào)用相關(guān)的事件處理程序(“派發(fā)事件”)關(guān)于如何處理事件

事件循環(huán)有時(shí)也被稱(chēng)為消息派發(fā)器、消息循環(huán)、消息泵或者運(yùn)行循環(huán)。

事件循環(huán)幾乎總是與消息發(fā)送者異步運(yùn)行

這里我覺(jué)得可以這么理解,“消息發(fā)送者”這邊將事件的消息交給了“事件提供者”,而事件循環(huán)這邊會(huì)向“事件提供者”發(fā)出請(qǐng)求獲取事件,然后調(diào)用相關(guān)的事件處理程序;所以說(shuō),事件循環(huán)與消息發(fā)送者是異步運(yùn)行。

事件循環(huán)必然是在“消息發(fā)送者”將事件的消息交出之后,才會(huì)去執(zhí)行事件處理程序;也就是說(shuō),事件循環(huán)的操作是在當(dāng)下之后,在”將來(lái)“才會(huì)發(fā)生的。

當(dāng)事件循環(huán)構(gòu)成程序的中心控制流結(jié)構(gòu)時(shí)(通常如此),它可以被稱(chēng)為主循環(huán)或主事件循環(huán)。這個(gè)稱(chēng)謂是恰當(dāng)?shù)模驗(yàn)檫@樣的事件循環(huán)處于程序的最高控制層。

MDN

WIKI上提供的是通用性的描述。我們?cè)倏匆幌翸DN,MDN上直接搜索事件循環(huán),可以看到是位于JavaScript路徑下,針對(duì)JavaScript事件循環(huán)的描述。

JavaScript has a runtime model based on an event loop, which is responsible for executing the code, collecting and processing events, and executing queued sub-tasks. This model is quite different from models in other languages like C and Java.

第一段很直白的描述:JavaScript的運(yùn)行時(shí)模型,是基于事件循環(huán)的,負(fù)責(zé)執(zhí)行代碼、收集和處理事件以及執(zhí)行隊(duì)列中的子任務(wù)。

執(zhí)行隊(duì)列中的子任務(wù):這基本上等于說(shuō),JavaScript的運(yùn)行時(shí)給JavaScript提供了事件循環(huán)的能力;可以說(shuō)JavaScript運(yùn)行時(shí)中事件循環(huán)的部分,提供了JavaScript異步的具體實(shí)現(xiàn)方式。給JavaScript提供了支持異步的能力。

那么JavaScript為什么要處理異步呢?這就不得不提JavaScript的單線(xiàn)程運(yùn)行特性 ,線(xiàn)程是什么?是進(jìn)行運(yùn)算調(diào)度的最小單位,而JavaScript設(shè)計(jì)之初,是為了處理網(wǎng)頁(yè)上的交互事件,如果JavaScript允許多線(xiàn)程,也就是允許多個(gè)觸發(fā)的事件同時(shí)進(jìn)行運(yùn)算,這可能就會(huì)呈現(xiàn)出各種不一樣的計(jì)算結(jié)果,在用戶(hù)看來(lái)就會(huì)顯得交互很混亂,為了減少不確定性,JavaScript干脆就選擇了單線(xiàn)程運(yùn)行,所有代碼都在同一個(gè)線(xiàn)程中執(zhí)行;另外,JavaScript中的交互事件很多,如果每個(gè)觸發(fā)事件都單獨(dú)開(kāi)辟線(xiàn)程來(lái)處理,也是不小的開(kāi)銷(xiāo)吧。

但是呢,雖然JavaScript是單線(xiàn)程運(yùn)行的,但也存在需要在將來(lái)完成的操作,也就是存在異步代碼,比如定時(shí)器。如果在Java中,我們也許可以選擇new一個(gè)線(xiàn)程,sleep多少秒,然后再執(zhí)行,但是JavaScript中不能這樣做,因?yàn)樗鼪](méi)有多線(xiàn)程,而如果直接在主線(xiàn)程等待,必定會(huì)引發(fā)阻塞和卡頓。事件循環(huán)就是對(duì)這種情況的一種解決方案,為了協(xié)調(diào)瀏覽器中的各種事件,必須使用事件循環(huán);而事件循環(huán)中的消息隊(duì)列就由JavaScript運(yùn)行時(shí)來(lái)管理。

運(yùn)行時(shí)概念

相信不少前端同學(xué)都聽(tīng)過(guò)“運(yùn)行時(shí)”這個(gè)詞,那運(yùn)行時(shí)到底是什么呢?我覺(jué)得可以這么簡(jiǎn)單理解,既然運(yùn)行時(shí)的功能是負(fù)責(zé)執(zhí)行代碼、收集和處理事件以及執(zhí)行隊(duì)列中的子任務(wù),那么運(yùn)行時(shí)中必須定義一套規(guī)則,關(guān)于如何去處理這些事情。所以可以簡(jiǎn)單地把運(yùn)行時(shí)認(rèn)為是定義了一套執(zhí)行規(guī)則的JavaScript執(zhí)行環(huán)境。

關(guān)于運(yùn)行時(shí),可以看到MDN上有一個(gè)直觀演示的圖,其中包含了函數(shù)調(diào)用形成的執(zhí)行棧、分配對(duì)象的堆,以及消息隊(duì)列。

根據(jù)WIKI給出的描述,運(yùn)行時(shí)模型中,與事件循環(huán)關(guān)系最密切的,是消息隊(duì)列,也就是我們前面提到的“事件提供者”。現(xiàn)在我們來(lái)看這個(gè)隊(duì)列。

A JavaScript runtime uses a message queue, which is a list of messages to be processed. Each message has an associated function that gets called to handle the message.

At some point during the event loop, the runtime starts handling the messages on the queue, starting with the oldest one. To do so, the message is removed from the queue and its corresponding function is called with the message as an input parameter. As always, calling a function creates a new stack frame for that function's use.

The processing of functions continues until the stack is once again empty. Then, the event loop will process the next message in the queue (if there is one).

我們來(lái)看翻譯的內(nèi)容:

JavaScript 運(yùn)行時(shí)使用消息隊(duì)列,這是一個(gè)待處理消息列表。每條消息都有一個(gè)相關(guān)函數(shù)被調(diào)用來(lái)處理該消息。

在事件循環(huán)中的某個(gè)時(shí)刻,運(yùn)行時(shí)開(kāi)始處理隊(duì)列中的消息,從最舊的消息開(kāi)始。(”隊(duì)“這個(gè)數(shù)據(jù)結(jié)構(gòu)我們知道,是先進(jìn)先出的,所以先進(jìn)隊(duì)的消息會(huì)先被處理。)為此,會(huì)從隊(duì)列中移除消息,并將消息作為輸入?yún)?shù)調(diào)用相應(yīng)的函數(shù)。一如既往,調(diào)用函數(shù)會(huì)創(chuàng)建一個(gè)新的堆棧框架供該函數(shù)使用。

函數(shù)的處理將一直持續(xù)到堆棧再次清空為止。然后,事件循環(huán)將處理隊(duì)列中的下一條消息(如果有的話(huà))。(也就是,消息隊(duì)列中的消息是一條接一條處理的。這里的堆棧指的就是函數(shù)調(diào)用形成的執(zhí)行棧和分配對(duì)象的堆)

那么隊(duì)列中的消息是哪里來(lái)的呢? 從這段內(nèi)容中我們可以知道,進(jìn)隊(duì)的消息已經(jīng)在等待處理了;所以比如有個(gè)定時(shí)器setTimeout,定義了有段代碼需要等待3秒才執(zhí)行,那這段代碼就不能直接就進(jìn)隊(duì),為了保證動(dòng)作3秒后才執(zhí)行,會(huì)在3秒后才進(jìn)隊(duì),也就是說(shuō),setTimeout的第二個(gè)參數(shù)代表的是將消息推入隊(duì)列的延遲時(shí)間。

那么肯定需要有什么東西,來(lái)管理這段代碼,將這段代碼在給定的延時(shí)后,推入消息隊(duì)列。既然js沒(méi)法去開(kāi)線(xiàn)程管理,所以也是瀏覽器在管理;Chrome就有一個(gè)定時(shí)器線(xiàn)程,專(zhuān)門(mén)用于處理定時(shí)器,在定時(shí)器計(jì)時(shí)結(jié)束后,通知事件觸發(fā)線(xiàn)程將消息推入隊(duì)列;同樣的,在用戶(hù)觸發(fā)交互事件時(shí),事件觸發(fā)線(xiàn)程也會(huì)將已在代碼中定義的消息推入隊(duì)列,也就是在事件監(jiān)聽(tīng)程序addEventListener中監(jiān)聽(tīng)的操作;還有異步HTTP請(qǐng)求線(xiàn)程,來(lái)管理請(qǐng)求回調(diào)的消息入隊(duì)。等等,瀏覽器的這些線(xiàn)程共同作用來(lái)實(shí)現(xiàn)事件循環(huán)這個(gè)機(jī)制。

在JS主線(xiàn)程空閑時(shí),就會(huì)將這些消息隊(duì)列中的消息出列,交由主線(xiàn)程來(lái)執(zhí)行。

那么接下來(lái)就是事件循環(huán)的執(zhí)行步驟的問(wèn)題。

事件循環(huán)執(zhí)行步驟

首先,關(guān)于微任務(wù):我們來(lái)看HTML的文檔。

Each event loop has a microtask queue, which is a queue of microtasks, initially empty. A microtask is a colloquial way of referring to a task that was created via the queue a microtask algorithm.

每個(gè)事件循環(huán)都有一個(gè)微任務(wù)隊(duì)列,這是一個(gè)初始為空的微任務(wù)隊(duì)列。微任務(wù)是一種通俗的說(shuō)法,指通過(guò)微任務(wù)隊(duì)列算法創(chuàng)建的任務(wù)。

也就是說(shuō),每個(gè)事件循環(huán)都會(huì)維護(hù)一個(gè)自己的微任務(wù)隊(duì)列。它和我們之前看的消息隊(duì)列,不是同一個(gè)隊(duì)列,消息隊(duì)列指的是這個(gè)文檔中的任務(wù)隊(duì)列,也就是task queue。

總所周知,常見(jiàn)的產(chǎn)生宏任務(wù)的方式有script、setTimeout、setInterval、UI事件等等;常見(jiàn)的產(chǎn)生微任務(wù)的方式有Promise.prototype.then、MutationObserver等等。

假設(shè)我們?cè)跒g覽器中加載了一個(gè)頁(yè)面,現(xiàn)在我們來(lái)看事件循環(huán)的處理步驟

  • 初始狀態(tài):運(yùn)行時(shí)的調(diào)用棧空。微任務(wù)隊(duì)列空,消息隊(duì)列里有且僅有一個(gè)script腳本(整體代碼)
  • 然后消息隊(duì)列中的script腳本被推入調(diào)用棧,同步代碼開(kāi)始執(zhí)行。
  • 當(dāng)碰到微任務(wù)時(shí),比如Promise.then,就將微任務(wù)推入事件循環(huán)的微任務(wù)隊(duì)列中;這里要注意一下,Promise執(zhí)行器函數(shù)中的代碼屬于同步代碼,會(huì)被順序執(zhí)行;
  • 當(dāng)碰到宏任務(wù)時(shí),就將它們丟給相應(yīng)的瀏覽器線(xiàn)程;
  • 當(dāng)本次代碼中的同步代碼都執(zhí)行完畢后,就將微任務(wù)隊(duì)列中的任務(wù)一一處理并出隊(duì);
  • 這樣就完成了一次循環(huán);
  • 本次的宏任務(wù)script腳本也被出隊(duì)。
  • 此時(shí)DOM修改完成,然后瀏覽器會(huì)執(zhí)行渲染操作,更新界面。
  • 如果宏任務(wù)在各自的線(xiàn)程中被處理完畢后,就會(huì)被推入消息隊(duì)列。
  • 再接著就是當(dāng)JS主線(xiàn)程空閑后,會(huì)去查詢(xún)隊(duì)列中是否還有任務(wù),開(kāi)啟新一輪的循環(huán)。

這個(gè)步驟我大概畫(huà)了個(gè)圖:

現(xiàn)在我們照著最開(kāi)始的面試題進(jìn)行舉例。

首先,這段代碼是一整個(gè)script腳本,其中的同步代碼會(huì)首先被按順序執(zhí)行,

可以看到這個(gè)script腳本中有一個(gè)async異步函數(shù),async函數(shù)中的同步代碼會(huì)首先被執(zhí)行,所以先會(huì)打印1

然后碰到兩個(gè)產(chǎn)生宏任務(wù)的setTimeout,丟給定時(shí)器線(xiàn)程,為了后面方便講述,這里分別把它們叫做宏任務(wù)1和宏任務(wù)2;

然后執(zhí)行promise執(zhí)行器函數(shù)中的同步代碼,打印4和6

接著碰到Promise.then這個(gè)微任務(wù),我們給它記為微任務(wù)1,將它推入微任務(wù)隊(duì)列,

然后我們又碰到一個(gè)await,await之后的代碼相當(dāng)于是Promise.then中的代碼,也就是會(huì)被推入微任務(wù)隊(duì)列,我們給它記為微任務(wù)2;

到這里,本次循環(huán)中的同步代碼都執(zhí)行完畢了;

接著就是開(kāi)始把微任務(wù)隊(duì)列中的微任務(wù)取出執(zhí)行,首先是執(zhí)行微任務(wù)1,打印5

接著執(zhí)行微任務(wù)2,打印7和8

本次事件循環(huán)就結(jié)束了。

等到計(jì)時(shí)結(jié)束,宏任務(wù)1會(huì)先被推入消息隊(duì)列,在JS主線(xiàn)程空閑,去查詢(xún)消息隊(duì)列后,代碼就會(huì)被執(zhí)行,會(huì)打印2

同理,宏任務(wù)2后面也會(huì)被執(zhí)行,并打印3

這樣我們就完成了這道面試題的解答。

總結(jié)

總的來(lái)說(shuō),事件循環(huán)就是JS中異步的具體實(shí)現(xiàn)方式,它的實(shí)現(xiàn)需要來(lái)自宿主環(huán)境的支持,比如瀏覽器中的各種線(xiàn)程,運(yùn)行時(shí)中的消息隊(duì)列等等。

總結(jié)

以上是生活随笔為你收集整理的浏览器事件循环Event Loop的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。