觀感度:🌟🌟🌟🌟🌟javascript
口味:甘梅地瓜html
烹飪時間:30min前端
想要了解JavaScript引擎,首先咱們從它的運行機制Event Loop
來講起。html5
首先科普一些基礎知識。
java
進程node
應用程序的執行實例,每個進程都是由私有的虛擬地址空間、代碼、數據和其餘系統資源所組成。web
線程面試
線程是進程內的一個獨立執行單元,在不一樣的線程之間是能夠共享進程資源的。api
有句老話是這樣說的,窮養兒子富養女。瀏覽器
進程就是一個富二代爸爸,它選擇了窮養線程兒子。
進程擁有獨立的堆棧空間和數據段,每當啓動一個新的進程必須分配給它獨立的地址空間,創建衆多的數據表來維護它的代碼段、堆棧段和數據段。
線程擁有獨立的堆棧空間,可是共享數據段,它們彼此之間使用相同的地址空間,共享大部分數據,比進程更節儉,開銷比較小,切換速度也比進程快,效率高。
一句話解釋進程和線程
進程:資源分配的最小單位
線程:程序執行的最小單位
關於進程和線程方面的知識咱們先了解到這,感興趣的同窗們能夠移步 進程和線程的區別
Q&A再來回答一個問題:
在多線程操做下能夠實現應用的並行處理,從而以更高的 CPU 利用率提升整個應用程序的性能和吞吐量。特別是如今不少語言都支持多核並行處理技術,然而 JavaScript 卻以單線程執行,爲何呢?
答:JavaScript做爲腳本語言,最初被設計用於瀏覽器。爲了不復雜的同步問題(作人嘛,仍是簡單點好,語言也同樣),若是JavaScript同時有兩個線程,一個線程中執行在某個DOM節點上添加內容,另外一個線程執行刪除這個節點,這時瀏覽器會……
因此JavaScript的單線程是這門語言的核心,將來也不會改變。
有人說,那HTML5的新特性Web Worker
,能夠建立多線程呀~
是的,爲了解決不可避免的耗時操做(多重循環、複雜的運算),HTML5提出了Web Worker
,它會在當前的js執行主線程中開闢出一個額外的線程來運行js文件,這個新的線程和js主線程之間不會互相影響,同時提供了數據交換的接口:postMessage
和onMessage
可是由於它建立的子線程徹底受控於主線程,且位於外部文件中,沒法訪問DOM。因此它並無改變js單線程的本質。
單線程就意味着,全部的任務都須要排隊。
就像還不能自助點餐的時候你去肯德基須要排隊,有的人沒想好點什麼或者點的東西不少,耗時就會長,那麼後面的人也只好排隊等待。有了自助點餐服務後,一切問題迎刃而解。
語言的設計和生活中的現實狀況很像,IO設備(輸入輸出)很慢(好比Ajax),那麼語言的設計者意識到這一點,就在主線程中掛起處於等待中的任務,先運行後面的任務,等IO設備有告終果,再把掛起的任務執行下去。從上圖中咱們能夠看到,在主線程運行時,會產生堆(heap)和棧(stack)。
堆中存的是咱們聲明的object類型的數據,棧中存的是基本數據類型以及函數執行時的運行空間。
棧中的代碼會調用各類外部API,它們在任務隊列中加入各類事件(onClick,onLoad,onDone),只要棧中的代碼執行完畢(js引擎存在monitoring process
進程,會持續不斷的檢查主線程執行棧是否爲空),主線程就回去讀取任務隊列,在按順序執行這些事件對應的回調函數。
也就是說主線程從任務隊列中讀取事件,這個過程是循環不斷的,因此這種運行機制又成爲Event Loop
(事件循環)。
咱們能夠將任務分爲同步任務和異步任務。
同步任務就是在主線程上排隊執行的任務,只能執行完一個再執行下一個。
異步任務則不進入主線程,而是先在event table中註冊函數,當知足觸發條件後,才能夠進入任務隊列來執行。只有任務隊列通知主線程說,我這邊異步任務能夠執行了,這個時候此任務纔會進入主線程執行。
舉個🌰
console.log(a);
setTimeout(
function () {
console.log(b);
},1000)
console.log(c)
// a
// c
// b
複製代碼
1.console.log(a)是同步任務,進入主線程執行,打印a。
2.setTimeout是異步任務,先被放入event table中註冊,1000ms以後進入任務隊列。
3.console.log(c)是同步任務,進入主線程執行,打印c。
當a,c被打印後,主線程去事件隊列中找到setTimeout裏的函數,並執行,打印b。
綜上所述,b最持久~(扯個🥚)
本文的Macrotask
在WHATWG
中叫task
。Macrotask
爲了便於理解,並無實際的出處。
同步任務和異步任務的劃分其實並不許確,準確的分類方式是宏任務(Macrotask)和微任務(Microtask)。
宏任務包括:script(總體代碼)
, setTimeout
, setInterval
, requestAnimationFrame
, I/O
,setImmediate
。
其中setImmediate
只存在於Node中,requestAnimationFrame
只存在於瀏覽器中。
微任務包括: Promise
, Object.observe
(已廢棄), MutationObserver
(html5新特性),process.nextTick
。
其中process.nextTick
只存在於Node中,MutationObserver
只存在於瀏覽器中。
注意:
UI Rendering
不屬於宏任務,也不屬於微任務,它是一個與微任務平行的一個操做步驟。 HTML規範文檔
這種分類的執行方式就是,執行一個宏任務,過程當中遇到微任務時,將其放到微任務的事件隊列裏,當前宏任務執行完成後,會查看微任務的事件隊列,依次執行裏面的微任務。若是還有宏任務的話,再從新開啓宏任務……
再舉個🌰
setTimeout(function() {
console.log('a')
});
new Promise(function(resolve) {
console.log('b');
for(var i =0; i <10000; i++) {
i ==99 && resolve();
}
}).then(function() {
console.log('c')
});
console.log('d');
// b
// d
// c
// a
複製代碼
1.首先執行script
下的宏任務,遇到setTimeout
,將其放入宏任務的隊列裏。
2.遇到Promise
,new Promise
直接執行,打印b。
3.遇到then
方法,是微任務,將其放到微任務的隊列裏。
4.遇到console.log('d')
,直接打印。
5.本輪宏任務執行完畢,查看微任務,發現then
方法裏的函數,打印c。
6.本輪event loop
所有完成。
7.下一輪循環,先執行宏任務,發現宏任務隊列中有一個setTimeout
,打印a。
綜上所述,不要說a是最持久的,若是你認爲你完全明白了,給你出道題,看看下面的代碼中,誰最持久?
console.log('a');
setTimeout(function() {
console.log('b');
process.nextTick(function() {
console.log('c');
})
new Promise(function(resolve) {
console.log('d');
resolve();
}).then(function() {
console.log('e')
})
})
process.nextTick(function() {
console.log('f');
})
new Promise(function(resolve) {
console.log('g');
resolve();
}).then(function() {
console.log('h')
})
setTimeout(function() {
console.log('i');
process.nextTick(function() {
console.log('j');
})
new Promise(function(resolve) {
console.log('k');
resolve();
}).then(function() {
console.log('l')
})
})
複製代碼
好,不要慫,咱們來逐步分析。
第一輪事件循環:
1.第一個宏任務(總體script)進入主線程,console.log('a')
,打印a。
2.遇到setTimeout
,其回調函數進入宏任務隊列,暫定義爲setTimeout1
。
3.遇到process.nextTick()
,其回調函數被分發到微任務隊列,暫定義爲process1
。
4.遇到Promise
,new Promise
直接執行,打印g。then
進入微任務隊列,暫定義爲then1
。
5.遇到setTimeout
,其回調函數進入宏任務隊列,暫定義爲setTimeout2
。
此時咱們看一下兩個任務隊列中的狀況
宏任務隊列 | 微任務隊列 |
---|---|
setTimeout一、setTimeout2 | process一、then1 |
第一輪宏任務執行完畢,打印出a和g。
查找微任務隊列中有process1
和then1
。所有執行,打印f和h。
第一輪事件循環完畢,打印出a、g、f和h。
第二輪事件循環:
1.從setTimeout1
宏任務開始,首先是console.lob('b')
,打印b。
2.遇到process.nextTick()
,進入微任務隊列,暫定義爲process2
。
3.new Promise
直接執行,輸出d,then
進入微任務隊列,暫定義爲then2
。
此時兩個任務隊列中
宏任務隊列 | 微任務隊列 |
---|---|
setTimeout2 | process二、 then2 |
第二輪宏任務執行完畢,打印出b和d。
查找微任務隊列中有process2
和then2
。所有執行,打印c和e。
第二輪事件循環完畢,打印出b、d、c和e。
第三輪事件循環
1.執行setTimeout2
,遇到console.log('i')
,打印i。
2.遇到process.nextTick()
,進入微任務隊列,暫定義爲process3
。
3.new Promise
直接執行,打印k。
4.then
進入微任務隊列,暫定義爲then3
。
此時兩個任務隊列中
宏任務隊列:空
微任務隊列:process3
、then3
第三輪宏任務執行完畢,打印出i和k。
查找微任務隊列中有process3
和then3
。所有執行,打印j和l。
第三輪事件循環完畢,打印出i、k、j和l。
到此爲止,三輪事件循環完畢,最終輸出結果爲:
a、g、f、h、b、d、c、e、i、k、j、l
複製代碼
l最持久,你答對了嗎?
以上代碼僅在瀏覽器環境中執行順序以下,node
環境下可能存在不一樣。
看完本文但願你可以理解JavaScript引擎的Event Loop
執行機制,不只可讓咱們更加深入的認識JavaScript這門語言,並且面試被問起的時候能夠和麪試官侃侃而談。
歡迎來個人我的公衆號交流,優質原創文章將同步推送。後臺回覆福利,便可領取福利,你懂得~
你的前端食堂,記得按時吃飯。