【转载】浏览器事件循环机制(event loop)
首先,本文轉(zhuǎn)自https://juejin.im/post/5afbc62151882542af04112d
當(dāng)我看完菲利普·羅伯茨的 javascript event loop的演講的時(shí)候,就對于事件循環(huán)很感興趣,于是查閱資料想寫一篇文章總結(jié)下,看了一些資料和博客,以及下筆嘗試后,感覺自己水平有限,看到作者的文筆和思路都很好的表達(dá)了我想要了解和困惑的東西,遂轉(zhuǎn)載過來。
JS是單線程的
JS是單線程的,或者說只有一個(gè)主線程,也就是它一次只能執(zhí)行一段代碼。JS中其實(shí)是沒有線程概念的,所謂的單線程也只是相對于多線程而言。JS的設(shè)計(jì)初衷就沒有考慮這些,針對JS這種不具備并行任務(wù)處理的特性,我們稱之為“單線程”。
雖然JS運(yùn)行在瀏覽器中是單線程的,但是瀏覽器是事件驅(qū)動的(Event driven),瀏覽器中很多行為是異步(Asynchronized)的,會創(chuàng)建事件并放入執(zhí)行隊(duì)列中。瀏覽器中很多異步行為都是由瀏覽器新開一個(gè)線程去完成,一個(gè)瀏覽器至少實(shí)現(xiàn)三個(gè)常駐線程:
- JS引擎線程
- GUI渲染線程
- 事件觸發(fā)線程
JS引擎
JavaScript引擎是一個(gè)專門處理JavaScript腳本的虛擬機(jī),一般會附帶在網(wǎng)頁瀏覽器之中,比如最出名的就是Chrome瀏覽器的V8引擎,如下圖所示,JS引擎主要有兩個(gè)組件構(gòu)成:
- 堆-內(nèi)存分配發(fā)生的地方
- 棧-函數(shù)調(diào)用時(shí)會形一個(gè)個(gè)棧幀(frame)?
執(zhí)行棧
每一個(gè)函數(shù)執(zhí)行的時(shí)候,都會生成新的execution context(執(zhí)行上下文),執(zhí)行上下文會包含一些當(dāng)前函數(shù)的參數(shù)、局部變量之類的信息,它會被推入棧中, running execution context(正在執(zhí)行的上下文)始終處于棧的頂部。當(dāng)函數(shù)執(zhí)行完后,它的執(zhí)行上下文會從棧彈出。
舉個(gè)簡單的例子:function bar() {
console.log('bar');
}function foo() {
console.log('foo');
bar();
}foo(); ?
執(zhí)行過程中棧的變化:
?
event loop(事件循環(huán))
Wikipedia這樣定義:
"Event Loop是一個(gè)程序結(jié)構(gòu),用于等待和發(fā)送消息和事件。(a programming construct that waits for and dispatches events or messages in a program.)"
簡單說,就是在程序中設(shè)置兩個(gè)線程:一個(gè)負(fù)責(zé)程序本身的運(yùn)行,稱為"主線程";另一個(gè)負(fù)責(zé)主線程與其他進(jìn)程(主要是各種I/O操作)的通信,被稱為"Event Loop線程"(可以譯為"消息線程")。
?
事件循環(huán)與任務(wù)隊(duì)列
事件循環(huán)可以簡單描述為:
- 函數(shù)入棧,當(dāng)Stack中執(zhí)行到異步任務(wù)的時(shí)候,就將他丟給WebAPIs,接著執(zhí)行同步任務(wù),直到Stack為空;
- 在此期間WebAPIs完成這個(gè)事件,把回調(diào)函數(shù)放入CallbackQueue中等待;
- 當(dāng)執(zhí)行棧為空時(shí),Event Loop把Callback Queue中的一個(gè)任務(wù)放入Stack中,回到第1步。
- Event Loop是由javascript宿主環(huán)境(像瀏覽器)來實(shí)現(xiàn)的;
- WebAPIs是由C++實(shí)現(xiàn)的瀏覽器創(chuàng)建的線程,處理諸如DOM事件、http請求、定時(shí)器等異步事件;
- JavaScript 的并發(fā)模型基于"事件循環(huán)";
- Callback Queue(Event Queue 或者 Message Queue) 任務(wù)隊(duì)列,存放異步任務(wù)的回調(diào)函數(shù)
接下來看一個(gè)異步函數(shù)執(zhí)行的例子:
var start=new Date();
setTimeout(function cb(){console.log("時(shí)間間隔:",new Date()-start+'ms');
},500);
while(new Date()-start<1000){}; - main(Script) 函數(shù)入棧,start變量開始初始化
- setTimeout入棧,出棧,丟給WebAPIs,開始定時(shí)500ms;
- while循環(huán)入棧,開始阻塞1000ms;
- 500ms過后,WebAPIs把cb()放入任務(wù)隊(duì)列,此時(shí)while循環(huán)還在棧中,cb()等待;
- 又過了500ms,while循環(huán)執(zhí)行完畢從棧中彈出,main()彈出,此時(shí)棧為空,Event Loop,cb()進(jìn)入棧,log()進(jìn)棧,輸出'時(shí)間間隔:1003ms',出棧,cb()出棧
宏任務(wù)(Macrotasks)和微任務(wù)(Microtasks)
其實(shí)我們上面所說的都是宏任務(wù)(Macrotasks),但是js中還有一種隊(duì)列微任務(wù)(Microtasks)。
macro-task(Task):一個(gè)event loop有一個(gè)或者多個(gè)task隊(duì)列。task任務(wù)源非常寬泛,比如ajax的onload,click事件,基本上我們經(jīng)常綁定的各種事件都是task任務(wù)源,還有數(shù)據(jù)庫操作(IndexedDB ),需要注意的是setTimeout、setInterval、setImmediate也是task任務(wù)源。總結(jié)來說task任務(wù)源:
- setTimeout
- setInterval
- setImmediate
- I/O
- UI rendering
micro-task(Job):microtask?隊(duì)列和task 隊(duì)列有些相似,都是先進(jìn)先出的隊(duì)列,由指定的任務(wù)源去提供任務(wù),不同的是一個(gè) event loop里只有一個(gè)microtask 隊(duì)列。另外microtask執(zhí)行時(shí)機(jī)和Macrotasks也有所差異
- process.nextTick
- promises
- Object.observe
- MutationObserver
那么Macrotasks和Microtasks有什么別區(qū)別呢
舉個(gè)簡單的例子,假設(shè)一個(gè)script標(biāo)簽的代碼如下:
Promise.resolve().then(function promise1 () {console.log('promise1');})
setTimeout(function setTimeout1 (){console.log('setTimeout1')Promise.resolve().then(function promise2 () {console.log('promise2'); }) }, 0) setTimeout(function setTimeout2 (){ console.log('setTimeout2') }, 0) 運(yùn)行過程:
script里的代碼被列為一個(gè)task,放入task隊(duì)列。
循環(huán)1:
- 【task隊(duì)列:script ;microtask隊(duì)列:】
- 從task隊(duì)列中取出script任務(wù),推入棧中執(zhí)行。
- promise1列為microtask,setTimeout1列為task,setTimeout2列為task。
- 【task隊(duì)列:setTimeout1 setTimeout2;microtask隊(duì)列:promise1】
- script任務(wù)執(zhí)行完畢,執(zhí)行microtask checkpoint,取出microtask隊(duì)列的promise1執(zhí)行。
循環(huán)2:
*【task隊(duì)列:setTimeout1 setTimeout2;microtask隊(duì)列:】 4. 從task隊(duì)列中取出setTimeout1,推入棧中執(zhí)行,將promise2列為microtask。
- 【task隊(duì)列:setTimeout2;microtask隊(duì)列:promise2】
- 執(zhí)行microtask checkpoint,取出microtask隊(duì)列的promise2執(zhí)行。
循環(huán)3:
- 【task隊(duì)列:setTimeout2;microtask隊(duì)列:】
- 從task隊(duì)列中取出setTimeout2,推入棧中執(zhí)行。 7.setTimeout2任務(wù)執(zhí)行完畢,執(zhí)行microtask checkpoint。
- 【task隊(duì)列:;microtask隊(duì)列:】
?
?
綜上所說,每次event loop循環(huán)執(zhí)行棧完成后,會繼續(xù)執(zhí)行完相應(yīng)的microtask任務(wù)
event loop中的Update the rendering(更新渲染)
這是event loop中很重要部分,在第7步會進(jìn)行Update the rendering(更新渲染),規(guī)范允許瀏覽器自己選擇是否更新視圖。也就是說可能不是每輪事件循環(huán)都去更新視圖,只在有必要的時(shí)候才更新視圖。
轉(zhuǎn)載于:https://www.cnblogs.com/chrissong/p/10582276.html
總結(jié)
以上是生活随笔為你收集整理的【转载】浏览器事件循环机制(event loop)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 海枯石烂下一句是什么呢?
- 下一篇: 第四周课上测试