javascript
技术干货 | JavaScript 之事件循环(Event Loop)
導(dǎo)讀:學(xué)過 JavaScript(下文簡稱 JS) 的都知道它是一門單線程的、非阻塞的腳本語言。單線程意味著,JS 代碼在執(zhí)行的任何時(shí)候,都只有一個(gè)主線程來處理所有的任務(wù),這也就意味著 JS 無法進(jìn)行多線程編程,但是 JS 當(dāng)中卻有著無處不在的異步概念,我們?nèi)绾卫斫饽?#xff1f;理解異步和非阻塞靠的就是 Event Loop(事件循環(huán)),本文就圍繞 JS 線程、同步異步、任務(wù)隊(duì)列等方面講解事件循環(huán)(Event Loop)。
文|倪萌
網(wǎng)易云信前端開發(fā)工程師
JS 線程
為了我們更方便容易了解事件循環(huán),在此之前我們先簡單了解下什么叫做 JS 線程。如瀏覽器的渲染進(jìn)程是多線程的,主要有以下幾個(gè)線程:
JS 引擎線程(主線程):負(fù)責(zé)解析 JS 腳本,運(yùn)行代碼。
GUI 渲染線程:負(fù)責(zé)渲染瀏覽器界面,解析 HTML、CSS、構(gòu) DOM 樹和 RenderObject 樹,布局和繪制等,當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流 (reflow) 時(shí),該線程就會(huì)執(zhí)行。
定時(shí)器觸發(fā)線程 (setTimeout):瀏覽器定時(shí)計(jì)數(shù)器并不是由 JS 引擎計(jì)數(shù)的,因?yàn)?JS 引擎是單線程的, 如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確,因此通過單獨(dú)線程來計(jì)時(shí)并觸發(fā)定時(shí),在計(jì)時(shí)完畢后,添加到事件隊(duì)列中,等待 JS 引擎空閑后執(zhí)行。
http 請(qǐng)求線程(ajax):XMLHttpRequest 連接后,通過瀏覽器新開一個(gè)線程請(qǐng)求,當(dāng)檢測(cè)到狀態(tài)變更時(shí),如果同時(shí)設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件,將這個(gè)回調(diào)再放入事件隊(duì)列中,再由 JS 引擎執(zhí)行。
瀏覽器事件觸發(fā)線程 (onclick):歸屬于瀏覽器而不是 JS 引擎,用來控制事件循環(huán),可以這么理解:JS 引擎自己都忙不過來,需要瀏覽器另開線程協(xié)助。
主線程和渲染線程互斥 :JS 引擎線程與 GUI 渲染線程是互斥的,當(dāng) JS 引擎執(zhí)行時(shí) GUI 線程會(huì)被掛起(相當(dāng)于被凍結(jié)了),GUI 更新會(huì)被保存在一個(gè)隊(duì)列中等到 JS 引擎空閑時(shí)立即被執(zhí)行。
瀏覽器內(nèi)核
EventLoop 輪詢處理線程:我們可以把它理解為一個(gè)中介,在主線程、異步線程與消息隊(duì)列三者之間進(jìn)行交流與溝通。如下圖所示:從主線程那里順時(shí)針的看,整個(gè)的流程是循環(huán)往復(fù)的。只有當(dāng)主線程的同步代碼都執(zhí)行完了,才會(huì)去隊(duì)列里看看還有什么要執(zhí)行的。
主線程把 setTimeout、ajax、dom.onclick 分別給三個(gè)線程,他們之間有些不同。
1、對(duì)于 setTimeout 代碼,定時(shí)器觸發(fā)線程在接收到代碼時(shí)就開始計(jì)時(shí),時(shí)間到了將回調(diào)函數(shù)扔進(jìn)消息隊(duì)列。
2、對(duì)于 ajax 代碼,http 異步線程立即發(fā)起 http 請(qǐng)求,請(qǐng)求成功后將回調(diào)函數(shù)扔進(jìn)消息隊(duì)列。
3、對(duì)于 dom.onclick,瀏覽器事件線程會(huì)先監(jiān)聽 dom,直到?dom 被點(diǎn)擊了,才將回調(diào)函數(shù)扔進(jìn)消息隊(duì)列。
同步與異步
JS 分為同步任務(wù)和異步任務(wù):
同步任務(wù):立即執(zhí)行的任務(wù)隊(duì)列,比如一個(gè)簡單的函數(shù);
異步任務(wù):請(qǐng)求接口發(fā)送 ajax,發(fā)送 promise,或時(shí)間計(jì)時(shí)器等等;
任務(wù)隊(duì)列(Event Queue)
什么是任務(wù)隊(duì)列呢?可以理解為一個(gè)靜態(tài)的隊(duì)列存儲(chǔ)結(jié)構(gòu),遵循先進(jìn)先出原則:同步任務(wù)會(huì)立刻執(zhí)行,進(jìn)入到主線程當(dāng)中;異步任務(wù)會(huì)被放到任務(wù)隊(duì)列(Event Queue)當(dāng)中。
宏任務(wù)隊(duì)列和微任務(wù)隊(duì)列
宏任務(wù)(MacroTask):整體代碼 Script、UI 渲染、setTimeout、setInterval、setImmediate(Node.js 環(huán)境)。
微任務(wù)(MicroTask):Promise.then()、catch、finally。
不同點(diǎn):event loop 里 MacroTask 隊(duì)列可能有多個(gè),MicroTask 隊(duì)列只有一個(gè)。
MicroTask 優(yōu)先于 MacroTask 執(zhí)行,所以如果有需要優(yōu)先執(zhí)行的邏輯,放入 MicroTask 隊(duì)列會(huì)比 MacroTask 更早的被執(zhí)行。
下面幾個(gè)代碼例子可以讓我們充分的了解各個(gè)任務(wù)之間的執(zhí)行順序:
執(zhí)行棧
MacroTask 和 MicroTask 都是推入棧中執(zhí)行的。JS 是單線程,也就是說只有一個(gè)主線程,主線程有一個(gè)棧,每一個(gè)函數(shù)執(zhí)行的時(shí)候,都會(huì)生成新的執(zhí)行上下文,執(zhí)行上下文會(huì)包含一些當(dāng)前函數(shù)的參數(shù)、局部變量之類的信息,它會(huì)被推入棧中,正在執(zhí)行的上下文始終處于棧的頂部。當(dāng)函數(shù)執(zhí)行完后,它的執(zhí)行上下文會(huì)從棧彈出。
總結(jié)
同步和異步任務(wù)分別進(jìn)入不同的執(zhí)行環(huán)境, 先執(zhí)行同步任務(wù),把異步任務(wù)放入循環(huán)隊(duì)列當(dāng)中,等待同步任務(wù)執(zhí)行完,再執(zhí)行隊(duì)列中的異步任務(wù)。異步任務(wù)先執(zhí)行微觀任務(wù),再執(zhí)行宏觀任務(wù)。一直這樣循環(huán),反復(fù)執(zhí)行,就是我們說的 Event Loop (事件循環(huán))。
事件循環(huán)是 JS 這門語言中非常重要且基礎(chǔ)的概念。讓我們可以清楚的了解事件循環(huán)的執(zhí)行順序和每一個(gè)階段的特點(diǎn),可以使我們對(duì)一段異步代碼的執(zhí)行順序有一個(gè)清晰的認(rèn)識(shí),從而減少代碼運(yùn)行的不確定性。合理的使用各種延遲事件的方法,有助于代碼更好的按照其優(yōu)先級(jí)去執(zhí)行。
如果在閱讀期間您發(fā)現(xiàn)了文章中的一些問題,歡迎在留言中提出,感謝您閱讀此文章。
?作者介紹?
倪萌,網(wǎng)易云信 web 前端開發(fā)工程師,目前在從事云信金融線業(yè)務(wù)相關(guān)開發(fā)工作。
?相關(guān)閱讀推薦?
技術(shù)系列課回顧 | 網(wǎng)易云信變聲技術(shù)之變調(diào)不變速算法
技術(shù)干貨 | C++20 四大特性之一:Module 特性詳解
技術(shù)實(shí)踐 | 網(wǎng)易云信視頻轉(zhuǎn)碼提速之分片轉(zhuǎn)碼
總結(jié)
以上是生活随笔為你收集整理的技术干货 | JavaScript 之事件循环(Event Loop)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 从0搭建在线聊天室,只需4步!
- 下一篇: 干货回顾 | 泛娱乐社交 APP 出海的