摘要:只要指定過(guò)回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入任務(wù)隊(duì)列,等待主線程讀取。三主線程從任務(wù)隊(duì)列中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為事件循環(huán)。
一、任務(wù)隊(duì)列 同步任務(wù)與異步任務(wù)的由來(lái)
單線程就意味著,所有任務(wù)需要排隊(duì),前一個(gè)任務(wù)結(jié)束,才會(huì)執(zhí)行后一個(gè)任務(wù)。如果前一個(gè)任務(wù)耗時(shí)很長(zhǎng),后一個(gè)任務(wù)就不得不一直等著。
如果排隊(duì)是因?yàn)橛?jì)算量大,CPU忙不過(guò)來(lái),倒也算了,但是很多時(shí)候CPU是閑著的,因?yàn)镮O設(shè)備(輸入輸出設(shè)備)很慢(比如Ajax操作從網(wǎng)絡(luò)讀取數(shù)據(jù)),不得不等著結(jié)果出來(lái),再往下執(zhí)行。
JavaScript語(yǔ)言的設(shè)計(jì)者意識(shí)到,這時(shí)主線程完全可以不管IO設(shè)備,掛起處于等待中的任務(wù),先運(yùn)行排在后面的任務(wù)。等到IO設(shè)備返回了結(jié)果,再回過(guò)頭,把掛起的任務(wù)繼續(xù)執(zhí)行下去。
于是,所有任務(wù)可以分成兩種,一種是同步任務(wù)(synchronous),另一種是異步任務(wù)(asynchronous)。
同步任務(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ī)制具體來(lái)說(shuō),異步執(zhí)行的運(yùn)行機(jī)制如下。(同步執(zhí)行也是如此,因?yàn)樗梢员灰暈闆](méi)有異步任務(wù)的異步執(zhí)行。)
(1)所有同步任務(wù)都在主線程上執(zhí)行,形成一個(gè)執(zhí)行棧(execution context stack)。
(2)主線程之外,還存在一個(gè)"任務(wù)隊(duì)列"(task queue)。只要異步任務(wù)有了運(yùn)行結(jié)果,就在"任務(wù)隊(duì)列"之中放置一個(gè)事件。
(3)一旦"執(zhí)行棧"中的所有同步任務(wù)執(zhí)行完畢,系統(tǒng)就會(huì)讀取"任務(wù)隊(duì)列",看看里面有哪些事件。那些對(duì)應(yīng)的異步任務(wù),于是結(jié)束等待狀態(tài),進(jìn)入執(zhí)行棧,開始執(zhí)行。
(4)主線程不斷重復(fù)上面的第三步。
只要主線程空了,就會(huì)去讀取"任務(wù)隊(duì)列",這就是JavaScript的運(yùn)行機(jī)制。這個(gè)過(guò)程會(huì)不斷重復(fù)。
舉個(gè)例子:
console.log("1"); setTimeout(()=>{ console.log("2"); },0); console.log("3"); // 1 // 3 // 2
運(yùn)行結(jié)果是:1、3、2
setTimeout里的函數(shù)并沒(méi)有立即執(zhí)行,而是延遲一段時(shí)間,符合特定的條件才開始執(zhí)行,這就是異步執(zhí)行操作。
console.log("1") //是同步任務(wù),放入主線程, setTimeout() //是異步任務(wù),被放入事件列表Event table中,0秒后被推入任務(wù)隊(duì)列task queue里, console.log("3") //是同步任務(wù),放入主線程 //當(dāng)1、3任務(wù)先執(zhí)行完后,主線程去task queue(事件隊(duì)列)里查看是否有可執(zhí)行的函數(shù),執(zhí)行setTimeout里的函數(shù)。二、事件和回調(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)擊、頁(yè)面滾動(dòng)等等)。只要指定過(guò)回調(diào)函數(shù),這些事件發(fā)生時(shí)就會(huì)進(jìn)入"任務(wù)隊(duì)列",等待主線程讀取。
所謂"回調(diào)函數(shù)"(callback),就是那些會(huì)被主線程掛起來(lái)的代碼。異步任務(wù)必須指定回調(diào)函數(shù),當(dāng)主線程開始執(zhí)行異步任務(wù),就是執(zhí)行對(duì)應(yīng)的回調(diào)函數(shù)。
"任務(wù)隊(duì)列"是一個(gè)先進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),排在前面的事件,優(yōu)先被主線程讀取。主線程的讀取過(guò)程基本上是自動(dòng)的,只要執(zhí)行棧一清空,"任務(wù)隊(duì)列"上第一位的事件就自動(dòng)進(jìn)入主線程。但是,由于存在后文提到的"定時(shí)器"功能,主線程首先要檢查一下執(zhí)行時(shí)間,某些事件只有到了規(guī)定的時(shí)間,才能返回主線程。
三、Event Loop主線程從"任務(wù)隊(duì)列"中讀取事件,這個(gè)過(guò)程是循環(huán)不斷的,所以整個(gè)的這種運(yùn)行機(jī)制又稱為Event Loop(事件循環(huán))。
主線程運(yùn)行的時(shí)候,產(chǎn)生堆(heap)和棧(stack),
heap(堆):是用戶主動(dòng)請(qǐng)求而劃分出來(lái)的內(nèi)存區(qū)域,比如你new Object(),就是將一個(gè)對(duì)象存入堆中,可以理解為heap存對(duì)象。
stack(棧):是由于函數(shù)運(yùn)行而臨時(shí)占用的內(nèi)存區(qū)域,函數(shù)都存放在棧里。
棧中的代碼調(diào)用各種外部API,它們?cè)?任務(wù)隊(duì)列"中加入各種事件(click,load,done)。
(當(dāng)滿足觸發(fā)條件后才加入隊(duì)列,如ajax請(qǐng)求完畢)
而當(dāng)棧中的代碼執(zhí)行完畢,主線程就會(huì)去讀取"任務(wù)隊(duì)列",依次執(zhí)行那些事件所對(duì)應(yīng)的回調(diào)函數(shù)。如此循環(huán)
【注意,總是要等待棧中的代碼執(zhí)行完畢后才會(huì)去讀取事件隊(duì)列中的事件】
四、宏任務(wù)和微任務(wù)JS中分為兩種任務(wù)類型:宏任務(wù)macro task和微任務(wù)micro task,宏任務(wù)與微任務(wù)的定義
在ECMAScript中,micro task稱為jobs,macro task可稱為task
1)宏任務(wù)(macro task),可以理解為每次執(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í)行開始前,對(duì)頁(yè)面進(jìn)行重新渲染
(task->渲染->task->...)
2)微任務(wù)(micro task),可以理解為在當(dāng)前 task 執(zhí)行結(jié)束后立即執(zhí)行的任務(wù)
也就是說(shuō),在當(dāng)前task任務(wù)后,下一個(gè)task之前,在渲染之前
所以它的響應(yīng)速度相比setTimeout(setTimeout是task)會(huì)更快,因?yàn)闊o(wú)需等渲染
也就是說(shuō),在某一個(gè)macro task執(zhí)行完后,就會(huì)將在它執(zhí)行期間產(chǎn)生的所有micro task都執(zhí)行完畢(在渲染前)
1)宏任務(wù)(macro task):
主代碼塊,setTimeout,setInterval,I/O、UI交互事件、postMessage、MessageChannel、setImmediate(node.js 環(huán)境)等(可以看到,事件隊(duì)列中的每一個(gè)事件都是一個(gè)宏任務(wù))
2)微任務(wù)(micro task):
Promise.then、MutaionObserver、MessageChannel、process.nextTick(node.js 環(huán)境)等
__補(bǔ)充:在node環(huán)境下,process.nextTick的優(yōu)先級(jí)高于Promise,也就是可以簡(jiǎn)單理解為:在宏任務(wù)結(jié)束后會(huì)先執(zhí)行微任務(wù)隊(duì)列中的nextTickQueue部分,然后才會(huì)執(zhí)行微任務(wù)中的Promise部分。
再根據(jù)線程來(lái)理解下:
宏任務(wù)(macro task)中的事件都是放在一個(gè)事件隊(duì)列中的,而這個(gè)隊(duì)列由事件觸發(fā)線程維護(hù)
微任務(wù)(micro task)中的所有微任務(wù)都是添加到微任務(wù)隊(duì)列(Job Queues)中,等待當(dāng)前宏任務(wù)執(zhí)行完畢后執(zhí)行,而這個(gè)隊(duì)列由JS引擎線程維護(hù)
所以,總結(jié)下運(yùn)行機(jī)制:
執(zhí)行一個(gè)宏任務(wù)(棧中沒(méi)有就從事件隊(duì)列中獲?。?/p>
執(zhí)行過(guò)程中如果遇到微任務(wù),就將它添加到微任務(wù)的任務(wù)隊(duì)列中
宏任務(wù)執(zhí)行完畢后,立即執(zhí)行當(dāng)前微任務(wù)隊(duì)列中的所有微任務(wù)(依次執(zhí)行)
當(dāng)前宏任務(wù)執(zhí)行完畢,開始檢查渲染,然后GUI線程接管渲染
渲染完畢后,JS線程繼續(xù)接管,開始下一個(gè)宏任務(wù)(從事件隊(duì)列中獲?。?/p>
舉個(gè)例子:
setTimeout(()=>{ console.log("定時(shí)器開始執(zhí)行"); }) new Promise(function(resolve){ console.log("準(zhǔn)備執(zhí)行for循環(huán)了"); for(var i=0;i<100;i++){ i==22&&resolve(); } }).then(()=>console.log("執(zhí)行then函數(shù)")); console.log("代碼執(zhí)行完畢"); //首先執(zhí)行script下的宏任務(wù),遇到setTimeout,將其放到宏任務(wù)的【隊(duì)列】里 //遇到 new Promise直接執(zhí)行,打印"準(zhǔn)備執(zhí)行for循環(huán)" //遇到then方法,是微任務(wù),將其放到微任務(wù)的【隊(duì)列里】 //打印 "代碼執(zhí)行完畢" //本輪宏任務(wù)執(zhí)行完畢,查看本輪的微任務(wù),發(fā)現(xiàn)有一個(gè)then方法里的函數(shù), 打印"執(zhí)行then函數(shù)" //到此,本輪的event loop 全部完成。 //下一輪的循環(huán)里,先執(zhí)行一個(gè)宏任務(wù),發(fā)現(xiàn)宏任務(wù)的【隊(duì)列】里有一個(gè) setTimeout里的函數(shù),執(zhí)行打印"定時(shí)器開始執(zhí)行"
所以最后的執(zhí)行順序就是:【準(zhǔn)備執(zhí)行for循環(huán)-->代碼執(zhí)行完畢-->執(zhí)行then函數(shù)-->定時(shí)器開始執(zhí)行】
如果你覺(jué)得這篇文章對(duì)你有所幫助,那就順便點(diǎn)個(gè)贊吧,點(diǎn)點(diǎn)關(guān)注不迷路~
黑芝麻哇,白芝麻發(fā),黑芝麻白芝麻哇發(fā)哈!
前端哇發(fā)哈
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/103040.html
摘要:令人困惑的是,文檔中稱,指定的回調(diào)函數(shù),總是排在前面。另外,由于指定的回調(diào)函數(shù)是在本次事件循環(huán)觸發(fā),而指定的是在下次事件循環(huán)觸發(fā),所以很顯然,前者總是比后者發(fā)生得早,而且執(zhí)行效率也高因?yàn)椴挥脵z查任務(wù)隊(duì)列。 一、定時(shí)器 除了放置異步任務(wù)的事件,任務(wù)隊(duì)列還可以放置定時(shí)事件,即指定某些代碼在多少時(shí)間之后執(zhí)行。這叫做定時(shí)器(timer)功能,也就是定時(shí)執(zhí)行的代碼。 定時(shí)器功能主要由setTim...
摘要:腳本執(zhí)行,事件處理等。引擎線程,也稱為內(nèi)核,負(fù)責(zé)處理腳本程序,例如引擎。事件觸發(fā)線程,用來(lái)控制事件循環(huán)可以理解為,引擎線程自己都忙不過(guò)來(lái),需要瀏覽器另開線程協(xié)助。異步請(qǐng)求線程,也就是發(fā)出請(qǐng)求后,接收響應(yīng)檢測(cè)狀態(tài)變更等都是這個(gè)線程管理的。 一、進(jìn)程與線程 現(xiàn)代操作系統(tǒng)比如Mac OS X,UNIX,Linux,Windows等,都是支持多任務(wù)的操作系統(tǒng)。 什么叫多任務(wù)呢?簡(jiǎn)單地說(shuō),就是操...
摘要:由此可知閉包是函數(shù)的執(zhí)行環(huán)境以及執(zhí)行環(huán)境中的函數(shù)組合而構(gòu)成的。此時(shí)產(chǎn)生了閉包。二閉包的作用閉包的特點(diǎn)是讀取函數(shù)內(nèi)部局部變量,并將局部變量保存在內(nèi)存,延長(zhǎng)其生命周期。三閉包的問(wèn)題使用閉包會(huì)將局部變量保持在內(nèi)存中,所以會(huì)占用大量?jī)?nèi)存,影響性能。 一、什么是閉包 1.閉包的定義 閉包是一種特殊的對(duì)象。它由兩部分構(gòu)成:函數(shù),以及創(chuàng)建該函數(shù)的環(huán)境(包含自由變量)。環(huán)境由閉包創(chuàng)建時(shí)在作用域中的任何...
摘要:在中,通過(guò)棧的存取方式來(lái)管理執(zhí)行上下文,我們可稱其為執(zhí)行棧,或函數(shù)調(diào)用棧。因?yàn)閳?zhí)行中最先進(jìn)入全局環(huán)境,所以處于棧底的永遠(yuǎn)是全局環(huán)境的執(zhí)行上下文。 一、什么是執(zhí)行上下文? 執(zhí)行上下文(Execution Context): 函數(shù)執(zhí)行前進(jìn)行的準(zhǔn)備工作(也稱執(zhí)行上下文環(huán)境) JavaScript在執(zhí)行一個(gè)代碼段之前,即解析(預(yù)處理)階段,會(huì)先進(jìn)行一些準(zhǔn)備工作,例如掃描JS中var定義的變量、...
摘要:全局作用域局部作用域局部作用域全局作用域局部作用域塊語(yǔ)句沒(méi)有塊級(jí)作用域塊級(jí)聲明包括和,以及和循環(huán),和函數(shù)不同,它們不會(huì)創(chuàng)建新的作用域。局部作用域只在該函數(shù)調(diào)用執(zhí)行期間存在。 一、什么是作用域? 作用域是你的代碼在運(yùn)行時(shí),各個(gè)變量、函數(shù)和對(duì)象的可訪問(wèn)性。(可產(chǎn)生作用的區(qū)域) 二、JavaScript中的作用域 在 JavaScript 中有兩種作用域 全局作用域 局部作用域 當(dāng)變量定...
閱讀 2108·2021-11-15 17:57
閱讀 789·2021-11-11 16:54
閱讀 2633·2021-09-27 13:58
閱讀 4220·2021-09-06 15:00
閱讀 990·2021-09-04 16:45
閱讀 3541·2019-08-30 15:56
閱讀 1810·2019-08-30 15:53
閱讀 1686·2019-08-30 14:12