摘要:引擎線程,也稱為內(nèi)核,負(fù)責(zé)處理腳本程序,例如引擎。異步請求線程,也就是發(fā)出請求后,接收響應(yīng)檢測狀態(tài)變更等都是這個線程管理的。為了解決這個問題,提出標(biāo)準(zhǔn),允許腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作。
本文主要參閱了以下兩篇文章,對JS的Event Loop運(yùn)行機(jī)制基礎(chǔ)知識進(jìn)行了整理。
從瀏覽器多進(jìn)程到JS單線程,JS運(yùn)行機(jī)制最全面的一次梳理
JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop
大家都知道JavaScript是單線程的,這就引申出一個問題,進(jìn)程與線程是什么,他們的區(qū)別是什么?
先給出進(jìn)程和線程的定義:
進(jìn)程是cpu資源分配的最小單位(是能擁有資源和獨(dú)立運(yùn)行的最小單位)
線程是cpu調(diào)度的最小單位(線程是建立在進(jìn)程的基礎(chǔ)上的一次程序運(yùn)行單位,一個進(jìn)程中可以有多個線程)
用工廠和工人的例子來形象闡述:
- 進(jìn)程是一個工廠,工廠有它的獨(dú)立資源 -> 系統(tǒng)分配的內(nèi)存(獨(dú)立的一塊內(nèi)存) - 工廠之間相互獨(dú)立 -> 進(jìn)程之間相互獨(dú)立 - 線程是工廠中的工人,多個工人協(xié)作完成任務(wù) -> 多個線程在進(jìn)程中協(xié)作完成任務(wù) - 工廠內(nèi)有一個或多個工人 -> 一個進(jìn)程由一個或多個線程組成 - 工人之間共享工廠的資源 -> 同一進(jìn)程下的各個線程之間共享進(jìn)程的內(nèi)存空間(包括代碼段、數(shù)據(jù)集、堆等)
補(bǔ)充:
我們所說的單線程和多線程,是指一個進(jìn)程內(nèi)是單一線程還是多線程。
進(jìn)程間的通信方式包括: 管道pipe、 命名管道FIFO、消息隊(duì)列MessageQueue、共享存儲SharedMemory、信號量Semaphore、套接字Socket、信號。
瀏覽器是多進(jìn)程的關(guān)于瀏覽器進(jìn)程問題可以簡單基礎(chǔ)三點(diǎn):
瀏覽器是多進(jìn)程的。
瀏覽器之所以能夠運(yùn)行,是因?yàn)橄到y(tǒng)給它的進(jìn)程分配了資源(cpu、內(nèi)存)。
簡單點(diǎn)理解,每打開一個Tab頁,就相當(dāng)于創(chuàng)建了一個獨(dú)立的瀏覽器進(jìn)程。
平時 coding 接觸到最多的一個瀏覽器進(jìn)程是瀏覽器渲染進(jìn)程(瀏覽器內(nèi)核),它管理著頁面渲染。腳本執(zhí)行,事件處理等。要同時處理這么多事情,渲染進(jìn)程顯然是多線程的,它主要包括以下5個常駐線程:
GUI渲染線程,負(fù)責(zé)渲染瀏覽器界面,解析HTML,CSS,構(gòu)建DOM樹和RenderObject樹,布局和繪制等。
JS引擎線程,也稱為JS內(nèi)核,負(fù)責(zé)處理Javascript腳本程序,(例如V8引擎)。
事件觸發(fā)線程,用來控制事件循環(huán)(可以理解為,JS引擎線程自己都忙不過來,需要瀏覽器另開線程協(xié)助)。
定時觸發(fā)器線程,瀏覽器定時計數(shù)器并不是由JavaScript引擎計數(shù)的,(因?yàn)镴avaScript引擎是單線程的, 如果處于阻塞線程狀態(tài)就會影響記計時的準(zhǔn)確),JS中常用的setInterval和setTimeout就歸這個線程管理。
異步http請求線程,也就是ajax發(fā)出http請求后,接收響應(yīng)、檢測狀態(tài)變更等都是這個線程管理的。
我們常說的JavaScript是單線程的,其實(shí)就是說的JS引擎是單線程的,它僅僅是瀏覽器渲染進(jìn)程種的一個線程。為什么呢?因?yàn)镴avaScript的主要作用是與用戶互動,以及操作DOM,如果JavaScript有兩個線程,一個線程對一個DOM節(jié)點(diǎn)執(zhí)行 A 操作,另一個線程這個DOM節(jié)點(diǎn)執(zhí)行 B 操作,那么就會起沖突,所以JavaScript在前端的應(yīng)用就注定了它是單線程的。
然而JavaScript的單線程特性就注定我們不用它去完成密集的 cpu 運(yùn)算,因?yàn)槊芗?cpu 運(yùn)算耗時過長,阻塞頁面渲染。為了解決這個問題,HTML5提出 Web Worker 標(biāo)準(zhǔn),允許JavaScript腳本創(chuàng)建多個線程,但是子線程完全受主線程控制,且不得操作DOM。
Event LoopJavaScript 是單線程的帶來的問題是:所有任務(wù)都必須同步執(zhí)行,問題就出現(xiàn)了,很多 I/O 過程是非常耗時的(如http 請求數(shù)據(jù)),如果要等到 I/O 過程結(jié)束再執(zhí)行后續(xù)任務(wù),就會出現(xiàn)頁面的卡頓、cpu 的閑置。于是異步的任務(wù)就出現(xiàn)了,異步任務(wù)是指掛起處于等待中的任務(wù),繼續(xù)執(zhí)行同步任務(wù),等到結(jié)果返回再去繼續(xù)執(zhí)行被掛起的任務(wù)。于是,JavaScript 的任務(wù)可以分為同步任務(wù)和異步任務(wù)。下面就引出 Event Loop 機(jī)制:
所有同步任務(wù)都在主線程上執(zhí)行,形成一個執(zhí)行棧
主線程之外,事件觸發(fā)線程管理著一個任務(wù)隊(duì)列,只要異步任務(wù)有了運(yùn)行結(jié)果,就在任務(wù)隊(duì)列之中放置一個事件。
一旦執(zhí)行棧中的所有同步任務(wù)執(zhí)行完畢(此時JS引擎空閑),系統(tǒng)就會讀取任務(wù)隊(duì)列,將可運(yùn)行的異步任務(wù)添加到執(zhí)行棧中,開始執(zhí)行。
如上圖所示,執(zhí)行棧中的代碼會調(diào)用一個異步的API,它們會在任務(wù)隊(duì)列中添加各種事件(或者說回調(diào)函數(shù)),另外用戶的操作如click、mousedown等都會在任務(wù)隊(duì)列中添加事件。只要執(zhí)行棧中的代碼執(zhí)行完畢,主線程就會去讀取任務(wù)隊(duì)列,將可執(zhí)行的回調(diào)函數(shù)放到執(zhí)行棧中執(zhí)行。
總結(jié)一下:
執(zhí)行棧執(zhí)行完畢 -> 主線程讀取任務(wù)隊(duì)列,并執(zhí)行回調(diào)函數(shù) -> 執(zhí)行棧執(zhí)行完畢 -> 主線程讀取任務(wù)隊(duì)列,并執(zhí)行回調(diào)函數(shù) ...
這個過程一直循環(huán)下去,所以就叫事件循環(huán)(Event Loop)。
setTimeout 和 setInterval前面提到了瀏覽器的定時觸發(fā)器線程,它的主要作用就是計時,setTimeout 和 setInterval 就由它來控制,原理就是到達(dá)設(shè)置時間后,往任務(wù)隊(duì)列中添加這兩個函數(shù)中指定的回調(diào)函數(shù)。
setTimeout() 方法用于在指定的毫秒數(shù)后調(diào)用函數(shù)或計算表達(dá)式。但是需要注意的是,實(shí)際是計時結(jié)束后定時觸發(fā)器線程才會將回調(diào)函數(shù)放到任務(wù)隊(duì)列中去,此時任務(wù)隊(duì)列中這個回調(diào)之前可能已經(jīng)有一些事件待處理,并且一定要執(zhí)行棧的任務(wù)執(zhí)行完后才會開始執(zhí)行任務(wù)隊(duì)列中的任務(wù),所以 setTimeout() 中回調(diào)開始執(zhí)行的時間是:執(zhí)行棧執(zhí)行時間 + 任務(wù)隊(duì)列前方回調(diào)執(zhí)行時間 + 延遲時間
setInterval() 方法可按照指定的時間間隔來周期性調(diào)用函數(shù)或計算表達(dá)式。它的問題在于:每次都精確的隔一段時間將一個回調(diào)放到任務(wù)隊(duì)列中,并沒有考慮到內(nèi)部回調(diào)函數(shù)執(zhí)行所需時間,這就會導(dǎo)致兩種問題:
回調(diào)函數(shù)執(zhí)行需要時間,兩個函數(shù)執(zhí)行的時間間隔會小于設(shè)定值;
如果回調(diào)函數(shù)執(zhí)行時間大于設(shè)定間隔,就會出現(xiàn)上一個加入任務(wù)隊(duì)列中的回調(diào)還沒執(zhí)行完,下一個回調(diào)就被加入任務(wù)隊(duì)列了,就會出現(xiàn)累計效應(yīng),即后面的回調(diào)會連續(xù)執(zhí)行。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/96459.html
摘要:規(guī)范中定義了瀏覽器何時進(jìn)行渲染更新,了解它有助于性能優(yōu)化。結(jié)合一些資料,對上邊規(guī)范給出一些理解有誤請指正每個線程都有自己的。列為,列為,列為。我們都知道是單線程,渲染計算和腳本運(yùn)行共用同一線程網(wǎng)絡(luò)請求會有其他線程,導(dǎo)致腳本運(yùn)行會阻塞渲染。 本文轉(zhuǎn)自blog 轉(zhuǎn)載請注明出處 異步的思考 event loops隱藏得比較深,很多人對它很陌生。但提起異步,相信每個人都知道。異步背后的靠山就是...
摘要:但是導(dǎo)致了很明顯的性能問題。上述兩個例子其實(shí)是在這個中找到的,第一個使用的版本是,這個版本的實(shí)現(xiàn)是采用了,而后因?yàn)榈睦锏挠校谑怯扔晗牧藢?shí)現(xiàn),換成了,也就是后一個所使用的。后來尤雨溪了解到是將回調(diào)放入的隊(duì)列。 結(jié)論 對于event loop 可以抽象成一段簡單的代碼表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...
摘要:但是導(dǎo)致了很明顯的性能問題。上述兩個例子其實(shí)是在這個中找到的,第一個使用的版本是,這個版本的實(shí)現(xiàn)是采用了,而后因?yàn)榈睦锏挠校谑怯扔晗牧藢?shí)現(xiàn),換成了,也就是后一個所使用的。后來尤雨溪了解到是將回調(diào)放入的隊(duì)列。 結(jié)論 對于event loop 可以抽象成一段簡單的代碼表示 for (macroTask of macroTaskQueue) { // 1. Handle cur...
摘要:事件循環(huán)了解了在引擎中是如何工作了之后,來看下如何使用異步回調(diào)函數(shù)來避免代碼。從回調(diào)函數(shù)被放入后秒鐘,把移到中。由于事件循環(huán)持續(xù)地監(jiān)測調(diào)用棧是否已空,此時它一注意到調(diào)用棧空了,就調(diào)用并創(chuàng)建一個新的調(diào)用棧。 聽多了JavaScript單線程,異步,V8,便會很想去知道JavaScript是如何利用單線程來實(shí)現(xiàn)所謂的異步的。我參考了一些文章,了解到一個很重要的詞匯:事件循環(huán)(Event L...
摘要:主線程在任務(wù)隊(duì)列中讀取事件,這個過程是循環(huán)不斷地,所以這種運(yùn)行機(jī)制叫做事件循環(huán)是在執(zhí)行棧同步代碼結(jié)束之后,下一次任務(wù)隊(duì)列執(zhí)行之前。 單線程 javascript為什么是單線程語言,原因在于如果是多線程,當(dāng)一個線程對DOM節(jié)點(diǎn)做添加內(nèi)容操作的時候,另一個線程要刪除這個DOM節(jié)點(diǎn),這個時候,瀏覽器應(yīng)該怎么選擇,這就造成了混亂,為了解決這類問題,在一開始的時候,javascript就采用單線...
閱讀 2428·2021-11-25 09:43
閱讀 1203·2021-09-07 10:16
閱讀 2623·2021-08-20 09:38
閱讀 2947·2019-08-30 15:55
閱讀 1467·2019-08-30 13:21
閱讀 897·2019-08-29 15:37
閱讀 1450·2019-08-27 10:56
閱讀 2100·2019-08-26 13:45