摘要:主線程要明確的一點(diǎn)是,主線程跟執(zhí)行棧是不同概念,主線程規(guī)定現(xiàn)在執(zhí)行執(zhí)行棧中的哪個(gè)事件。主線程循環(huán)即主線程會(huì)不停的從執(zhí)行棧中讀取事件,會(huì)執(zhí)行完所有棧中的同步代碼。以上參考資料詳解中的事件循環(huán)機(jī)制中的事件循環(huán)運(yùn)行機(jī)制詳解再談
前言
大家都知道js是單線程的腳本語言,在同一時(shí)間,只能做同一件事,為了協(xié)調(diào)事件、用戶交互、腳本、UI渲染和網(wǎng)絡(luò)處理等行為,防止主線程阻塞,Event Loop方案應(yīng)運(yùn)而生...
個(gè)人博客了解一下:obkoro1.com為什么js是單線程?
js作為主要運(yùn)行在瀏覽器的腳本語言,js主要用途之一是操作DOM。
在js高程中舉過一個(gè)栗子,如果js同時(shí)有兩個(gè)線程,同時(shí)對(duì)同一個(gè)dom進(jìn)行操作,這時(shí)瀏覽器應(yīng)該聽哪個(gè)線程的,如何判斷優(yōu)先級(jí)?
為了避免這種問題,js必須是一門單線程語言,并且在未來這個(gè)特點(diǎn)也不會(huì)改變。
執(zhí)行棧與任務(wù)隊(duì)列因?yàn)閖s是單線程語言,當(dāng)遇到異步任務(wù)(如ajax操作等)時(shí),不可能一直等待異步完成,再繼續(xù)往下執(zhí)行,在這期間瀏覽器是空閑狀態(tài),顯而易見這會(huì)導(dǎo)致巨大的資源浪費(fèi)。
執(zhí)行棧當(dāng)執(zhí)行某個(gè)函數(shù)、用戶點(diǎn)擊一次鼠標(biāo),Ajax完成,一個(gè)圖片加載完成等事件發(fā)生時(shí),只要指定過回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入執(zhí)行棧隊(duì)列中,等待主線程讀取,遵循先進(jìn)先出原則。
主線程要明確的一點(diǎn)是,主線程跟執(zhí)行棧是不同概念,主線程規(guī)定現(xiàn)在執(zhí)行執(zhí)行棧中的哪個(gè)事件。
主線程循環(huán):即主線程會(huì)不停的從執(zhí)行棧中讀取事件,會(huì)執(zhí)行完所有棧中的同步代碼。
當(dāng)遇到一個(gè)異步事件后,并不會(huì)一直等待異步事件返回結(jié)果,而是會(huì)將這個(gè)事件掛在與執(zhí)行棧不同的隊(duì)列中,我們稱之為任務(wù)隊(duì)列(Task Queue)。
當(dāng)主線程將執(zhí)行棧中所有的代碼執(zhí)行完之后,主線程將會(huì)去查看任務(wù)隊(duì)列是否有任務(wù)。如果有,那么主線程會(huì)依次執(zhí)行那些任務(wù)隊(duì)列中的回調(diào)函數(shù)。
不太理解的話,可以運(yùn)行一下下面的代碼,或者點(diǎn)擊一下這個(gè)demo
結(jié)果是當(dāng)a、b、c函數(shù)都執(zhí)行完成之后,三個(gè)setTimeout才會(huì)依次執(zhí)行。
let a = () => { setTimeout(() => { console.log("任務(wù)隊(duì)列函數(shù)1") }, 0) for (let i = 0; i < 5000; i++) { console.log("a的for循環(huán)") } console.log("a事件執(zhí)行完") } let b = () => { setTimeout(() => { console.log("任務(wù)隊(duì)列函數(shù)2") }, 0) for (let i = 0; i < 5000; i++) { console.log("b的for循環(huán)") } console.log("b事件執(zhí)行完") } let c = () => { setTimeout(() => { console.log("任務(wù)隊(duì)列函數(shù)3") }, 0) for (let i = 0; i < 5000; i++) { console.log("c的for循環(huán)") } console.log("c事件執(zhí)行完") } a(); b(); c(); // 當(dāng)a、b、c函數(shù)都執(zhí)行完成之后,三個(gè)setTimeout才會(huì)依次執(zhí)行js 異步執(zhí)行的運(yùn)行機(jī)制。
所有任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧。
主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件。
一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列"。那些對(duì)應(yīng)的異步任務(wù),結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧并開始執(zhí)行。
主線程不斷重復(fù)上面的第三步。
宏任務(wù)與微任務(wù):異步任務(wù)分為 宏任務(wù)(macrotask) 與 微任務(wù) (microtask),不同的API注冊(cè)的任務(wù)會(huì)依次進(jìn)入自身對(duì)應(yīng)的隊(duì)列中,然后等待 Event Loop 將它們依次壓入執(zhí)行棧中執(zhí)行。
宏任務(wù)(macrotask)::
script(整體代碼)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 環(huán)境)
微任務(wù)(microtask):
Promise、 MutaionObserver、process.nextTick(Node.js環(huán)境)
Event Loop(事件循環(huán)):Event Loop(事件循環(huán))中,每一次循環(huán)稱為 tick, 每一次tick的任務(wù)如下:
執(zhí)行棧選擇最先進(jìn)入隊(duì)列的宏任務(wù)(通常是script整體代碼),如果有則執(zhí)行
檢查是否存在 Microtask,如果存在則不停的執(zhí)行,直至清空 microtask 隊(duì)列
更新render(每一次事件循環(huán),瀏覽器都可能會(huì)去更新渲染)
重復(fù)以上步驟
宏任務(wù) > 所有微任務(wù) > 宏任務(wù),如下圖所示:
從上圖我們可以看出:
將所有任務(wù)看成兩個(gè)隊(duì)列:執(zhí)行隊(duì)列與事件隊(duì)列。
執(zhí)行隊(duì)列是同步的,事件隊(duì)列是異步的,宏任務(wù)放入事件列表,微任務(wù)放入執(zhí)行隊(duì)列之后,事件隊(duì)列之前。
當(dāng)執(zhí)行完同步代碼之后,就會(huì)執(zhí)行位于執(zhí)行列表之后的微任務(wù),然后再執(zhí)行事件列表中的宏任務(wù)
上面提到的demo結(jié)果可以這么理解:先執(zhí)行script宏任務(wù),執(zhí)行完了之后,再執(zhí)行其他兩個(gè)定時(shí)器宏任務(wù)。
面試題實(shí)踐下面這個(gè)題,很多人都應(yīng)該看過/遇到過,重新來看會(huì)不會(huì)覺得清晰很多:
// 執(zhí)行順序問題,考察頻率挺高的,先自己想答案** setTimeout(function () { console.log(1); }); new Promise(function(resolve,reject){ console.log(2) resolve(3) }).then(function(val){ console.log(val); }) console.log(4);
根據(jù)本文的解析,我們可以得到:
先執(zhí)行script同步代碼
先執(zhí)行new Promise中的console.log(2),then后面的不執(zhí)行屬于微任務(wù)
然后執(zhí)行console.log(4)
執(zhí)行完script宏任務(wù)后,執(zhí)行微任務(wù),console.log(3),沒有其他微任務(wù)了。
執(zhí)行另一個(gè)宏任務(wù),定時(shí)器,console.log(1)。
根據(jù)本文的內(nèi)容,可以很輕松,且有理有據(jù)的猜出寫出正確答案:2,4,3,1.
類似上文的面試題還有很多,實(shí)則都大同小異,只要掌握了事件循環(huán)的機(jī)制,這些問題都會(huì)變得很簡(jiǎn)單。
文章如有不正確的地方歡迎各位路過的大佬鞭策!希望大家看完可以有所收獲,喜歡的話,趕緊點(diǎn)波訂閱關(guān)注/喜歡。
個(gè)人blog and 掘金個(gè)人主頁(yè),如需轉(zhuǎn)載,請(qǐng)放上原文鏈接并署名。碼字不易,感謝支持!
如果喜歡本文的話,歡迎關(guān)注我的訂閱號(hào),漫漫技術(shù)路,期待未來共同學(xué)習(xí)成長(zhǎng)。
以上2018.6.16
參考資料:詳解JavaScript中的Event Loop(事件循環(huán))機(jī)制
JavaScript中的事件循環(huán) Event Loop
JavaScript 運(yùn)行機(jī)制詳解:再談Event Loop
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/95513.html
摘要:設(shè)置和清除定時(shí)器直接引用忍者秘籍中的圖片注意定時(shí)器的時(shí)間間隔設(shè)為,也會(huì)有幾毫秒的延遲。以上參考資料忍者秘籍第章馴服線程和定時(shí)器 showImg(https://segmentfault.com/img/remote/1460000015353524?w=1024&h=681); 前言 前段時(shí)間剛看完《JS忍者秘籍》,雖說是15年出版的,有些東西是過時(shí)了,但像對(duì)原型鏈、閉包、正則、定時(shí)器...
摘要:圖片轉(zhuǎn)引自的演講和兩個(gè)定時(shí)器中回調(diào)的執(zhí)行邏輯便是典型的機(jī)制。異步編程關(guān)于異步編程我的理解是,在執(zhí)行環(huán)境所提供的異步機(jī)制之上,在應(yīng)用編碼層面上實(shí)現(xiàn)整體流程控制的異步風(fēng)格。 問題背景 在一次開發(fā)任務(wù)中,需要實(shí)現(xiàn)如下一個(gè)餅狀圖動(dòng)畫,基于canvas進(jìn)行繪圖,但由于對(duì)于JS運(yùn)行環(huán)境中異步機(jī)制的不了解,所以遇到了一個(gè)棘手的問題,始終無法解決,之后在與同事交流之后才恍然大悟。問題的根節(jié)在于經(jīng)典的J...
摘要:瀏覽器與的異同,以及部分機(jī)制有人對(duì)部分迷惑,本身構(gòu)造函數(shù)是同步的,是異步。瀏覽器的的已全部分析完成,過程中引用阮一峰博客,知乎,部分文章內(nèi)容,侵刪。 瀏覽器與NodeJS的EventLoop異同,以及部分機(jī)制 PS:有人對(duì)promise部分迷惑,Promise本身構(gòu)造函數(shù)是同步的,.then是異步。---- 2018/7/6 22:35修改 javascript 是一門單線程的腳本...
摘要:了解事件循環(huán)機(jī)制有助于理解的執(zhí)行過程,同時(shí)這也是面試常見題。那么這個(gè)回調(diào)函數(shù)將在何時(shí)由誰執(zhí)行呢已知是瀏覽器環(huán)境提供的,因此瀏覽器將對(duì)它進(jìn)行處理,瀏覽器會(huì)在本次事件完成,即計(jì)時(shí)結(jié)束后,將回調(diào)函數(shù)加入循環(huán)隊(duì)列中,然后等待被加入執(zhí)行棧執(zhí)行。 如果有人問JavaScript是什么,也許你會(huì)說它是一個(gè)單線程、非阻塞、異步、解釋型的腳本語言。那么作為一個(gè)單線程語言,它是怎么實(shí)現(xiàn)非阻塞、異步的?這就...
閱讀 2710·2021-11-16 11:53
閱讀 2775·2021-07-26 23:38
閱讀 2102·2019-08-30 15:55
閱讀 1793·2019-08-30 13:21
閱讀 3716·2019-08-29 17:26
閱讀 3379·2019-08-29 13:20
閱讀 905·2019-08-29 12:20
閱讀 3231·2019-08-26 10:21