JS運(yùn)行機(jī)制

進(jìn)程和線程

多線程可以并行處理任務(wù),但是線程是不能單獨(dú)存在的宇立,它是由進(jìn)程來啟動(dòng)和管理的。

一個(gè)進(jìn)程就是一個(gè)程序的運(yùn)行實(shí)例自赔。詳細(xì)解釋就是妈嘹,啟動(dòng)一個(gè)程序的時(shí)候,操作系統(tǒng)會(huì)為該程序創(chuàng)建一塊內(nèi)存绍妨,用來存放代碼润脸、運(yùn)行中的數(shù)據(jù)和一個(gè)執(zhí)行任務(wù)的主線程,我們把這樣的一個(gè)運(yùn)行環(huán)境叫進(jìn)程痘绎。

單線程與多線程的進(jìn)程對比圖

從圖中可以看到津函,線程是依附于進(jìn)程的肖粮,而進(jìn)程中使用多線程并行處理能提升運(yùn)算效率孤页。

形象的比喻:

  • 進(jìn)程是一個(gè)工廠,工廠有它的獨(dú)立資源
  • 工廠之間相互獨(dú)立
  • 線程是工廠中的工人涩馆,多個(gè)工人協(xié)作完成任務(wù)
  • 工廠內(nèi)有一個(gè)或多個(gè)工人
  • 工人之間共享空間

再完善完善概念:

  • 工廠的資源 -> 系統(tǒng)分配的內(nèi)存(獨(dú)立的一塊內(nèi)存)
  • 工廠之間的相互獨(dú)立 -> 進(jìn)程之間相互獨(dú)立
  • 多個(gè)工人協(xié)作完成任務(wù) -> 多個(gè)線程在進(jìn)程中協(xié)作完成任務(wù)
  • 工廠內(nèi)有一個(gè)或多個(gè)工人 -> 一個(gè)進(jìn)程由一個(gè)或多個(gè)線程組成
  • 工人之間共享空間 -> 同一進(jìn)程下的各個(gè)線程之間共享程序的內(nèi)存空間(包括代碼段行施、數(shù)據(jù)集、堆等)

總結(jié)來說魂那,進(jìn)程和線程之間的關(guān)系有以下 4 個(gè)特點(diǎn)蛾号。

  1. 進(jìn)程中的任意一線程執(zhí)行出錯(cuò),都會(huì)導(dǎo)致整個(gè)進(jìn)程的崩潰涯雅。
  2. 線程之間共享進(jìn)程中的數(shù)據(jù)鲜结。
    線程之間可以對進(jìn)程的公共數(shù)據(jù)進(jìn)行讀寫操作。


    線程之間共享進(jìn)程中的數(shù)據(jù)示意圖

    從上圖可以看出,線程 1精刷、線程 2拗胜、線程 3 分別把執(zhí)行的結(jié)果寫入 A、B怒允、C 中埂软,然后線程 2 繼續(xù)從 A、B纫事、C 中讀取數(shù)據(jù)勘畔,用來顯示執(zhí)行結(jié)果。

  3. 當(dāng)一個(gè)進(jìn)程關(guān)閉之后丽惶,操作系統(tǒng)會(huì)回收進(jìn)程所占用的內(nèi)存炫七。
  4. 進(jìn)程之間的內(nèi)容相互隔離。
    進(jìn)程隔離是為保護(hù)操作系統(tǒng)中進(jìn)程互不干擾的技術(shù)钾唬,每一個(gè)進(jìn)程只能訪問自己占有的數(shù)據(jù)诉字,也就避免出現(xiàn)進(jìn)程 A 寫入數(shù)據(jù)到進(jìn)程 B 的情況。正是因?yàn)檫M(jìn)程之間的數(shù)據(jù)是嚴(yán)格隔離的知纷,所以一個(gè)進(jìn)程如果崩潰了壤圃,或者掛起了琅轧,是不會(huì)影響到其他進(jìn)程的。如果進(jìn)程之間需要進(jìn)行數(shù)據(jù)的通信乍桂,這時(shí)候,就需要使用用于進(jìn)程間通信(IPC)的機(jī)制了睹酌。

多進(jìn)程瀏覽器時(shí)代

  • 瀏覽器是多進(jìn)程的
  • 瀏覽器之所以能夠運(yùn)行,是因?yàn)橄到y(tǒng)給它的進(jìn)程分配了資源(CPU憋沿、內(nèi)存)
  • 簡單點(diǎn)理解旺芽,每打開一個(gè)Tab頁辐啄,就相當(dāng)于創(chuàng)建了一個(gè)獨(dú)立的瀏覽器進(jìn)程

瀏覽器多進(jìn)程架構(gòu)

Chrome 進(jìn)程架構(gòu)圖

從圖中可以看出,Chrome 瀏覽器包括:1 個(gè)瀏覽器(Browser)主進(jìn)程壶辜、1 個(gè) GPU 進(jìn)程悯舟、1 個(gè)網(wǎng)絡(luò)(NetWork)進(jìn)程、多個(gè)渲染進(jìn)程和多個(gè)插件進(jìn)程砸民。

這幾個(gè)進(jìn)程的功能:

  1. 瀏覽器主進(jìn)程:主要負(fù)責(zé)界面顯示抵怎、用戶交互奋救、子進(jìn)程管理,同時(shí)提供存儲(chǔ)等功能反惕。
  2. 插件進(jìn)程:主要是負(fù)責(zé)插件的運(yùn)行菠镇,因插件易崩潰,所以需要通過插件進(jìn)程來隔離承璃,以保證插件進(jìn)程崩潰不會(huì)對瀏覽器和頁面造成影響利耍。
  3. GPU進(jìn)程:用于3D CSS 的效果。
  4. 渲染進(jìn)程:核心任務(wù)是將HTML盔粹、CSS和JavaScript轉(zhuǎn)換為用戶可以與之交互的網(wǎng)頁隘梨,排版引擎Blink和JavaScript引擎V8都是運(yùn)行在該進(jìn)程中,默認(rèn)情況下舷嗡,Chrome 會(huì)為每個(gè)Tab標(biāo)簽創(chuàng)建一個(gè)渲染進(jìn)程轴猎。出于安全考慮,渲染進(jìn)程都是運(yùn)行在沙箱模式下进萄。
  5. 網(wǎng)絡(luò)進(jìn)程捻脖。主要負(fù)責(zé)頁面的網(wǎng)絡(luò)資源加載。

打開一個(gè)頁面需要啟動(dòng)多少進(jìn)程

打開1個(gè)頁面至少需要1個(gè)網(wǎng)絡(luò)進(jìn)程中鼠、1個(gè)瀏覽器進(jìn)程可婶、1個(gè) GPU 進(jìn)程以及1個(gè)渲染進(jìn)程,共4個(gè)援雇;如果打開的頁面有運(yùn)行插件的話矛渴,還需要再加上1個(gè)插件進(jìn)程。

瀏覽器多進(jìn)程的優(yōu)勢

相比于單進(jìn)程瀏覽器惫搏,多進(jìn)程有如下優(yōu)點(diǎn):

  • 避免單個(gè)page crash影響整個(gè)瀏覽器
  • 避免第三方插件crash影響整個(gè)瀏覽器
  • 多進(jìn)程充分利用多核優(yōu)勢
  • 方便使用沙盒模型隔離插件等進(jìn)程,提高瀏覽器穩(wěn)定性

當(dāng)然筐赔,內(nèi)存等資源消耗也會(huì)更大,有點(diǎn)空間換時(shí)間的意思达皿。
簡單點(diǎn)理解:如果瀏覽器是單進(jìn)程,那么某個(gè)Tab頁崩潰了鳞绕,就影響了整個(gè)瀏覽器尸曼;同理如果是單進(jìn)程萄焦,插件崩潰了也會(huì)影響整個(gè)瀏覽器冤竹。

重點(diǎn)是瀏覽器內(nèi)核(渲染進(jìn)程)

瀏覽器的渲染進(jìn)程是多線程的茬射。
可以這樣理解,頁面的渲染钟病,JS的執(zhí)行刚梭,事件的循環(huán),都在這個(gè)進(jìn)程內(nèi)進(jìn)行朴读。
接下來列舉一些主要常駐線程:

GUI渲染線程

負(fù)責(zé)渲染瀏覽器界面,解析HTML噪伊,CSS氮唯,構(gòu)建DOM樹和Render樹,布局和繪制等惩琉。
當(dāng)界面需要重繪(Repaint)或由于某種操作引發(fā)回流(reflow)時(shí),該線程就會(huì)執(zhí)行肆糕。
注意在孝,GUI渲染線程與JS引擎線程是互斥的,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起(相當(dāng)于被凍結(jié)了)私沮,GUI更新會(huì)被保存在一個(gè)隊(duì)列中等到JS引擎空閑時(shí)立即被執(zhí)行。

JS引擎線程

也稱為JS內(nèi)核造垛,負(fù)責(zé)處理Javascript腳本程序晰搀。(例如V8引擎)
JS引擎線程負(fù)責(zé)解析Javascript腳本,運(yùn)行代碼外恕。
JS引擎一直等待著任務(wù)隊(duì)列中任務(wù)的到來乡翅,然后加以處理罪郊,一個(gè)Tab頁(renderer進(jìn)程)中無論什么時(shí)候都只有一個(gè)JS線程在運(yùn)行JS程序。
同樣注意靶累,GUI渲染線程與JS引擎線程是互斥的,所以如果JS執(zhí)行的時(shí)間過長挣柬,這樣就會(huì)造成頁面的渲染不連貫争舞,導(dǎo)致頁面渲染加載阻塞。

事件觸發(fā)線程

歸屬于瀏覽器而不是JS引擎店溢,用來控制事件循環(huán)(可以理解,JS引擎自己都忙不過來床牧,需要瀏覽器另開線程協(xié)助)遭贸。
當(dāng)JS引擎執(zhí)行代碼塊如setTimeout時(shí)(也可來自瀏覽器內(nèi)核的其他線程,如鼠標(biāo)點(diǎn)擊著蛙、AJAX異步請求等),會(huì)將對應(yīng)任務(wù)添加到事件線程中踏堡。
當(dāng)對應(yīng)的事件符合觸發(fā)條件被觸發(fā)時(shí)咒劲,該線程會(huì)把事件添加到待處理隊(duì)列的隊(duì)尾,等待JS引擎的處理帐偎。
注意,由于JS的單線程關(guān)系蛔屹,所以這些待處理隊(duì)列中的事件都得排隊(duì)等待JS引擎處理(當(dāng)JS引擎空閑時(shí)才會(huì)去執(zhí)行)。

定時(shí)觸發(fā)器線程

setIntervalsetTimeout所在線程嫉父。
瀏覽器定時(shí)計(jì)數(shù)器并不是由JavaScript引擎計(jì)數(shù)的,(因?yàn)镴avaScript引擎是單線程的眼刃,如果處于阻塞線程狀態(tài)就會(huì)影響記計(jì)時(shí)的準(zhǔn)確)。
因此通過單獨(dú)線程來計(jì)時(shí)并觸發(fā)定時(shí)(計(jì)時(shí)完畢后擂红,添加到事件隊(duì)列中,等待JS引擎空閑后執(zhí)行)昵骤。
注意,在HTML標(biāo)準(zhǔn)中規(guī)定成榜,要求setTimeout中低于4ms的時(shí)間間隔算為4ms。

異步http請求線程

XMLHttpRequest在連接后是通過瀏覽器新開一個(gè)線程請求赎婚。
將檢測到狀態(tài)變更時(shí)樱溉,如果設(shè)置有回調(diào)函數(shù),異步線程就產(chǎn)生狀態(tài)變更事件撩嚼,將這個(gè)回調(diào)再放入事件隊(duì)列中挖帘。再由JavaScript引擎執(zhí)行。

Browser進(jìn)程和瀏覽器內(nèi)核(Renderer進(jìn)程)的通信過程

打開任務(wù)管理器逻族,然后打開一個(gè)瀏覽器,就可以看到:任務(wù)管理器中出現(xiàn)了兩個(gè)進(jìn)程(一個(gè)是主控進(jìn)程瓷耙,一個(gè)則是打開Tab頁的渲染進(jìn)程)刁赖,然后在這前提下,看下整個(gè)的過程:(簡化了很多)

  • Browser進(jìn)程收到用戶請求鸡典,首先需要獲取頁面內(nèi)容(譬如通過網(wǎng)絡(luò)下載資源),隨后將該任務(wù)通過RendererHost接口傳遞給Render進(jìn)程
  • Renderer進(jìn)程的Renderer接口收到消息彻况,簡單解釋后,交給渲染線程纽甘,然后開始渲染
    • 渲染線程接收請求,加載網(wǎng)頁并渲染網(wǎng)頁悍赢,這其中可能需要Browser進(jìn)程獲取資源和需要GPU進(jìn)程來幫助渲染
    • 當(dāng)然可能會(huì)有JS線程操作DOM(這樣可能會(huì)造成回流并重繪)
    • 最后Render進(jìn)程將結(jié)果傳遞給Browser進(jìn)程
  • Browser進(jìn)程接收到結(jié)果并將結(jié)果繪制出來

瀏覽器內(nèi)核中線程之間的關(guān)系

GUI渲染線程與JS引擎線程互斥

由于JavaScript是可操縱DOM的左权,如果在修改這些元素屬性同時(shí)渲染界面(即JS線程和UI線程同時(shí)運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了赏迟。
因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置GUI渲染線程與JS引擎為互斥的關(guān)系甩栈,當(dāng)JS引擎執(zhí)行時(shí)GUI線程會(huì)被掛起抛丽,GUI更新則會(huì)被保存在一個(gè)隊(duì)列中等到JS引擎線程空閑時(shí)立即被執(zhí)行。

JS阻塞頁面加載

從上述的互斥關(guān)系亿鲜,可以推導(dǎo)出,JS如果執(zhí)行時(shí)間過長就會(huì)阻塞頁面饶套。
譬如垒探,假設(shè)JS引擎正在進(jìn)行巨量的計(jì)算,此時(shí)就算GUI有更新圾叼,也會(huì)被保存到隊(duì)列中,等待JS引擎空閑后執(zhí)行构挤。
然后,由于巨量計(jì)算筋现,所以JS引擎很可能很久很久后才能空閑,自然會(huì)感覺到巨卡無比一膨。
所以,要盡量避免JS執(zhí)行時(shí)間過長豹绪,這樣就會(huì)造成頁面的渲染不連貫微谓,導(dǎo)致頁面渲染加載阻塞的感覺输钩。

從Event Loop談JS的運(yùn)行機(jī)制

任務(wù)隊(duì)列

單線程就意味著,所有任務(wù)需要排隊(duì)买乃,前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù)肴焊。如果前一個(gè)任務(wù)耗時(shí)很長功戚,后一個(gè)任務(wù)就必須一直等到前一個(gè)任務(wù)結(jié)束才能執(zhí)行。但這時(shí)CPU是閑著的啸臀,這樣浪費(fèi)了很多計(jì)算機(jī)的性能。
JavaScript語言的設(shè)計(jì)者意識到豌注,這時(shí)主線程完全可以不管IO設(shè)備,掛起處于等待中的任務(wù)轧铁,先運(yùn)行排在后面的任務(wù)旦棉。等到IO設(shè)備返回了結(jié)果,再回過頭救斑,把掛起的任務(wù)繼續(xù)執(zhí)行下去。

于是系谐,所有任務(wù)可以分成兩種,一種是同步任務(wù)鄙煤,另一種是異步任務(wù)。
同步任務(wù)指的是梯刚,在主線程上排隊(duì)執(zhí)行的任務(wù)薪寓,只有前一個(gè)任務(wù)執(zhí)行完畢,才能執(zhí)行后一個(gè)任務(wù)向叉。
異步任務(wù)指的是,不進(jìn)入主線程瘦黑、而進(jìn)入"任務(wù)隊(duì)列"(task queue)的任務(wù)奇唤,只有"任務(wù)隊(duì)列"通知主線程,某個(gè)異步任務(wù)可以執(zhí)行了咬扇,該任務(wù)才會(huì)進(jìn)入主線程執(zhí)行。

具體來說经窖,異步執(zhí)行的運(yùn)行機(jī)制如下。(同步執(zhí)行也是如此钠至,因?yàn)樗梢员灰暈闆]有異步任務(wù)的異步執(zhí)行胎源。)

  1. 所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧宪卿。
  2. 主線程之外,事件觸發(fā)線程管理著一個(gè)"任務(wù)隊(duì)列"(task queue)佑钾。只要異步任務(wù)有了運(yùn)行結(jié)果烦粒,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件代赁。
  3. 一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢兽掰,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列",看看里面有哪些事件窖壕。那些對應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài)瞻讽,進(jìn)入執(zhí)行棧熏挎,開始執(zhí)行。
  4. 主線程不斷重復(fù)上面的第三步婆瓜。

我們不禁要問了,那怎么知道主線程執(zhí)行棧為空啊乖寒?js引擎存在monitoring process進(jìn)程,會(huì)持續(xù)不斷的檢查主線程執(zhí)行棧是否為空磅轻,一旦為空,就會(huì)去"任務(wù)隊(duì)列"那里檢查是否有等待被調(diào)用的函數(shù)聋溜。這就是JavaScript的運(yùn)行機(jī)制叭爱。這個(gè)過程會(huì)不斷重復(fù)。

事件和回調(diào)函數(shù)

"任務(wù)隊(duì)列"是一個(gè)事件的隊(duì)列(也可以理解成消息的隊(duì)列)买雾,IO設(shè)備完成一項(xiàng)任務(wù),就在"任務(wù)隊(duì)列"中添加一個(gè)事件嗤军,表示相關(guān)的異步任務(wù)可以進(jìn)入"執(zhí)行棧"了晃危。主線程讀取"任務(wù)隊(duì)列",就是讀取里面有哪些事件。

"任務(wù)隊(duì)列"中的事件沿量,除了IO設(shè)備的事件以外冤荆,還包括一些用戶產(chǎn)生的事件(比如鼠標(biāo)點(diǎn)擊、頁面滾動(dòng)等等)钓简。只要指定過回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入"任務(wù)隊(duì)列"撤蚊,等待主線程讀取。

所謂"回調(diào)函數(shù)"(callback)侦啸,就是那些會(huì)被主線程掛起來的代碼丧枪。異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)主線程開始執(zhí)行異步任務(wù)拧烦,就是執(zhí)行對應(yīng)的回調(diào)函數(shù)。

"任務(wù)隊(duì)列"是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu)齐佳,排在前面的事件,優(yōu)先被主線程讀取炼吴。主線程的讀取過程基本上是自動(dòng)的疫衩,只要執(zhí)行棧一清空,"任務(wù)隊(duì)列"上第一位的事件就自動(dòng)進(jìn)入主線程隧土。但是,由于存在后文提到的"定時(shí)器"功能曹傀,主線程首先要檢查一下執(zhí)行時(shí)間,某些事件只有到了規(guī)定的時(shí)間嗜价,才能返回主線程。

Event Loop

主線程從"任務(wù)隊(duì)列"中讀取事件久锥,這個(gè)過程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))瑟由。

上圖中,主線程運(yùn)行的時(shí)候青伤,產(chǎn)生堆(heap)和棧(stack)殴瘦,棧中的代碼調(diào)用各種外部API,它們在"任務(wù)隊(duì)列"中加入各種事件(click蚪腋,load,done)立帖。只要棧中的代碼執(zhí)行完畢,主線程就會(huì)去讀取"任務(wù)隊(duì)列"厘惦,依次執(zhí)行那些事件所對應(yīng)的回調(diào)函數(shù)哩簿。
執(zhí)行棧中的代碼(同步任務(wù))节榜,總是在讀取"任務(wù)隊(duì)列"(異步任務(wù))之前執(zhí)行。

瀏覽器向我們提供了JS引擎不具備的特性:Web API宗苍。Web API包括DOM API薄榛、定時(shí)器、HTTP請求等特性敞恋,可以幫助我們實(shí)現(xiàn)異步、非阻塞的行為补箍。

當(dāng)我們調(diào)用一個(gè)函數(shù)時(shí)改执,函數(shù)會(huì)被放入一個(gè)叫做調(diào)用棧(call stack辈挂,也叫執(zhí)行上下文棧)的地方裹粤。調(diào)用棧是JS引擎的一部分,并非瀏覽器特有的遥诉。調(diào)用棧是一個(gè)棧數(shù)據(jù)結(jié)構(gòu),具有后進(jìn)先出的特點(diǎn)(Last in, first out. LIFO)突那。當(dāng)函數(shù)執(zhí)行完畢返回時(shí),會(huì)被彈出調(diào)用棧早龟。

圖例中的respond函數(shù)返回一個(gè)setTimeout函數(shù)調(diào)用猫缭,setTimeout函數(shù)是Web API提供給我們的功能:它允許我們延遲執(zhí)行一個(gè)任務(wù)而不用阻塞主線程。setTimeout被調(diào)用時(shí)猜丹,我們傳入的回調(diào)函數(shù),即箭頭函數(shù)()=>{return'hey'}會(huì)被傳遞給Web API處理藏杖,然后setTimeoutrespond依次執(zhí)行完畢出棧脉顿。

在Web API中會(huì)執(zhí)行定時(shí)器,定時(shí)間隔就是我們傳入setTimeout的第二個(gè)參數(shù)艾疟,也就是1000ms。計(jì)時(shí)結(jié)束后回調(diào)函數(shù)并不會(huì)立即進(jìn)入調(diào)用棧執(zhí)行弟疆,而是會(huì)被加入一個(gè)叫做 任務(wù)隊(duì)列(Task Queue)的地方盗冷。

Event Loop的工作就是連接任務(wù)隊(duì)列和調(diào)用棧,當(dāng)調(diào)用棧中的任務(wù)均執(zhí)行完畢出棧正塌,調(diào)用棧為空時(shí)恤溶,Event Loop會(huì)檢查任務(wù)隊(duì)列中是否存在等待執(zhí)行的任務(wù)咒程,如果存在,則取出隊(duì)列中第一個(gè)任務(wù)帐姻,放入調(diào)用棧奶段。

我們的回調(diào)函數(shù)被放入調(diào)用棧中,執(zhí)行完畢呢铆,返回其返回值,然后被彈出調(diào)用棧棺克。

下面代碼輸出什么:

const foo = () => console.log('First');
const bar = () => setTimeout(() => console.log('Second'), 500);
const baz = () => console.log('Third');

bar();
foo();
baz();
  • bar被調(diào)用线定,返回setTimeout的調(diào)用;
  • 傳入setTimeout的回調(diào)被傳遞給Web API處理纱皆,setTimeout執(zhí)行完畢出棧芭商,bar執(zhí)行完畢出棧;
  • 定時(shí)器開始運(yùn)行铛楣,同時(shí)主線程中foo被調(diào)用,打印First蛉艾,foo執(zhí)行完畢出棧勿侯;
  • baz被調(diào)用,打印Third助琐,baz執(zhí)行完畢出棧面氓;
  • 500ms后定時(shí)器運(yùn)行完畢蛆橡,回調(diào)函數(shù)被放入任務(wù)隊(duì)列掘譬;
  • Event Loop檢測到調(diào)用棧為空,從任務(wù)隊(duì)列中取出回調(diào)函數(shù)放入調(diào)用棧葱轩;
  • 回調(diào)函數(shù)被執(zhí)行,打印Second垃喊,執(zhí)行完畢出棧。

定時(shí)器

除了放置異步任務(wù)的事件本谜,"任務(wù)隊(duì)列"還可以放置定時(shí)事件偎窘,即指定某些代碼在多少時(shí)間之后執(zhí)行。這叫做"定時(shí)器"功能评架,也就是定時(shí)執(zhí)行的代碼。
定時(shí)器功能主要由setTimeout()setInterval()這兩個(gè)函數(shù)來完成纵诞,它們的內(nèi)部運(yùn)行機(jī)制完全一樣,區(qū)別在于前者指定的代碼是一次性執(zhí)行登刺,后者則為反復(fù)執(zhí)行。setTimeout()接受兩個(gè)參數(shù)纸俭,第一個(gè)是回調(diào)函數(shù)揍很,第二個(gè)是推遲執(zhí)行的毫秒數(shù)。

console.log(1);
setTimeout(function(){console.log(2);},1000);
console.log(3);

上面代碼的執(zhí)行結(jié)果是1窒悔,3敌买,2。
如果將setTimeout()的第二個(gè)參數(shù)設(shè)為0虹钮,就表示當(dāng)前代碼執(zhí)行完(執(zhí)行棧清空)以后膘融,立即執(zhí)行(0毫秒間隔)指定的回調(diào)函數(shù)祭玉。

setTimeout(function(){console.log(1);}, 0);
console.log(2);

總之,setTimeout(fn,0)的含義是攘宙,指定某個(gè)任務(wù)在主線程最早可得的空閑時(shí)間執(zhí)行,也就是說疗绣,盡可能早得執(zhí)行。它在"任務(wù)隊(duì)列"的尾部添加一個(gè)事件多矮,因此要等到同步任務(wù)和"任務(wù)隊(duì)列"現(xiàn)有的事件都處理完哈打,才會(huì)得到執(zhí)行。

H5標(biāo)準(zhǔn)規(guī)定了setTimeout()的第二個(gè)參數(shù)的最小值(最短間隔)料仗,不得低于4毫秒,如果低于這個(gè)值立轧,就會(huì)自動(dòng)增加。在此之前帐萎,老版本的瀏覽器都將最短間隔設(shè)為10毫秒。另外疆导,對于那些DOM的變動(dòng)(尤其是涉及頁面重新渲染的部分)葛躏,通常不會(huì)立即執(zhí)行,而是每16毫秒執(zhí)行一次舰攒。這時(shí)使用requestAnimationFrame()的效果要好于setTimeout()

需要注意的是芒率,setTimeout()只是將事件插入了"任務(wù)隊(duì)列"偶芍,必須等到當(dāng)前代碼(執(zhí)行棧)執(zhí)行完充择,主線程才會(huì)去執(zhí)行它指定的回調(diào)函數(shù)椎麦。要是當(dāng)前代碼耗時(shí)很長材彪,有可能要等很久,所以并沒有辦法保證段化,回調(diào)函數(shù)一定會(huì)在setTimeout()指定的時(shí)間執(zhí)行。

宏任務(wù)與微任務(wù)

除了廣義的同步任務(wù)和異步任務(wù)之外显熏,還有對任務(wù)更精細(xì)的劃分,分為:
macrotask(又稱之為宏任務(wù))缓升,可以理解是每次執(zhí)行棧執(zhí)行的代碼就是一個(gè)宏任務(wù)(包括每次從事件隊(duì)列中獲取一個(gè)事件回調(diào)并放到執(zhí)行棧中執(zhí)行)

  • 每一個(gè)task會(huì)從頭到尾將這個(gè)任務(wù)執(zhí)行完畢,不會(huì)執(zhí)行其它
  • 瀏覽器為了能夠使得JS內(nèi)部task與DOM任務(wù)能夠有序的執(zhí)行港谊,會(huì)在一個(gè)task執(zhí)行結(jié)束后橙弱,在下一個(gè)task執(zhí)行開始前,對頁面進(jìn)行重新渲染(task->渲染->task->...

microtask(又稱為微任務(wù))膘螟,可以理解是在當(dāng)前task執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)

  • 也就是說,在當(dāng)前task任務(wù)后荆残,下一個(gè)task之前,在渲染之前
  • 所以它的響應(yīng)速度相比setTimeoutsetTimeouttask)會(huì)更快蕴潦,因?yàn)闊o需等渲染
  • 也就是說,在某一個(gè)macrotask執(zhí)行完后潭苞,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有microtask都執(zhí)行完畢(在渲染前)

在ECMAScript中真朗,microtask稱為jobsmacrotask可稱為task
形成macrotaskmicrotask的場景:

  • macrotask:主代碼塊湖笨、setTimeout、setInterval
  • microtaskPromise慈省、process.nextTick

不同類型的任務(wù)會(huì)進(jìn)入對應(yīng)的任務(wù)隊(duì)列眠菇,比如setTimeoutsetInterval會(huì)進(jìn)入相同的任務(wù)隊(duì)列。
事件循環(huán)的順序捎废,決定js代碼的執(zhí)行順序。進(jìn)入整體代碼(宏任務(wù))后缕坎,開始第一次循環(huán)。接著執(zhí)行所有的微任務(wù)谜叹。然后再次從宏任務(wù)開始,找到其中一個(gè)任務(wù)隊(duì)列執(zhí)行完畢艳悔,再執(zhí)行所有的微任務(wù)。

所以js運(yùn)行過程:

  • 執(zhí)行一個(gè)宏任務(wù)(棧中沒有就從事件隊(duì)列中獲炔履辍)
  • 執(zhí)行過程中如果遇到微任務(wù)疾忍,就將它添加到微任務(wù)的任務(wù)隊(duì)列中
  • 宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
  • 當(dāng)前宏任務(wù)執(zhí)行完畢一罩,開始檢查渲染,然后GUI線程接管渲染
  • 渲染完畢后聂渊,JS線程繼續(xù)接管,開始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲扔印)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末饼暑,一起剝皮案震驚了整個(gè)濱河市洗做,隨后出現(xiàn)的幾起案子迈着,更是在濱河造成了極大的恐慌,老刑警劉巖裕菠,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奴潘,死亡現(xiàn)場離奇詭異,居然都是意外死亡画髓,警方通過查閱死者的電腦和手機(jī)平委,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來廉赔,“玉大人,你說我怎么就攤上這事蜡塌。” “怎么了馏艾?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長铁孵。 經(jīng)常有香客問我,道長蜕劝,這世上最難降的妖魔是什么志膀? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮溉浙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戳稽。我一直安慰自己期升,他們只是感情好互躬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著容为,像睡著了一般。 火紅的嫁衣襯著肌膚如雪坎背。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天寄雀,我揣著相機(jī)與錄音,去河邊找鬼懂更。 笑死,一個(gè)胖子當(dāng)著我的面吹牛沮协,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播皂股,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼命黔,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了悍募?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤洋魂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后副砍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡豁翎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年隅忿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了邦尊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片优烧。...
    茶點(diǎn)故事閱讀 40,137評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖又沾,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情捍掺,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站曲横,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏禾嫉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一熙参、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧孽椰,春花似錦、人聲如沸黍匾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至霎终,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莱褒,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工保礼, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炮障。 一個(gè)月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像胁赢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子智末,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評論 2 355