在計算機內存中存取數據,基本的數據結構分爲棧和隊列。javascript
棧(Stack)是一種後進先出的數據結構,注意,有時候也管棧叫作「堆棧」,可是「堆」又是另外一種複雜的數據結構,它和棧徹底是兩碼事。棧的特色是操做只在一端進行,通常來講,棧的操做只有兩種:進棧和出棧。第一個進棧的數據老是最後一個纔出來。html
隊列(Queue)和棧相似,但它是先進先出的數據結構,插入數據的操做從隊列的一端進行,而刪除的操做在另外一端。java
通俗的比喻棧就像是一個立好的桶,先放入棧的數據會放在桶底,出棧時會在桶口一一將數據取出,因此最早放入棧的數據老是最後一個才能取出。而隊列就像是一個水管,最早放入隊列的數據會第一個從隊列的另外一端流出,這是它們最大的區別。node
在javascript中,函數的執行就一個典型的入棧與出棧的過程:面試
function fun1() { function fun2() { function fun3() { console.log('do it'); } fun3(); } fun2(); } fun1();
在程序執行時,首先將fun1,fun2,fun3依次入棧,而在調用函數時,是先將fun3調用(出棧),再是fun2和fun1,試想一下,若是fun1先出棧,那麼函數fun2和fun3必將丟失。chrome
在javascript這門語言中程序是單線程的,只有一個主線程,這是爲何?由於不難想像,最初javascript的設計是跑在瀏覽器中的腳本語言,若是設計成多線程,兩個線程同時修改DOM那以誰的爲準呢?因此javascript爲單線程,在一個線程中代碼會一句一句向下走,直到程序跑完,若中間有較爲費時的操做,那也只能等着。瀏覽器
單線程的設計使得語言的執行效率不好,爲了利用多核心CPU的性能,javascript語言支持異步代碼,當有較爲費時的操做時,可將任務寫爲異步執行,當一個異步任務尚未執行完時,主線程會將異步任務掛起,繼續執行後面的同步代碼,以後再回過頭來看,若是有異步任務運行完了再執行它。數據結構
這種執行代碼的方式其實很符合咱們生活中的不少場景,好比小明同窗下班回家了,他很渴,想燒水泡茶,若是是同步的執行方式那就是燒水,在水沒開時小明像個傻子似的等着,等水開了再泡茶;如果異步執行,小明先開始燒水,而後就去幹點別的事,好比看會電視、聽聽音樂,等水燒開了再去泡茶。明顯第二種異步方式效率更高。多線程
常見的異步操做都有哪些?有不少,咱們能夠羅列幾個常見的:異步
咱們先來看一段代碼:
//示例1 console.log(1); setTimeout(function () { console.log(2); }, 1000); console.log(3);
這段代碼很是簡單,把它們放在瀏覽器中執行結果以下:
1 3 2
由於setTimeout函數延時了1000毫秒執行,所以先輸出1和3,而2是過了1000毫秒以後再輸出,這很合邏輯。
咱們稍稍改動一下代碼,將setTimeout的延時時間改成0:
//示例2 console.log(1); setTimeout(function () { console.log(2); }, 0); //0毫秒,不延時 console.log(3);
運行結果:
1 3 2
爲何延時了0毫秒仍是最後輸出的2?先別急,咱們再來看一段代碼:
//示例3 console.log(1); setTimeout(function () { console.log(2); }, 0); Promise.resolve().then(function(){ console.log(3); }); console.log(4);
運行結果:
1 4 3 2
以上三段代碼,若是你能正確的寫出結果,而且能說明白爲何這樣輸出,說明你對javascript的事件環理解的很清楚,若是講不出來,咱們就一塊兒聊聊這裏面發生了什麼,其實頗有意思。
一開始先簡單聊了聊基本的數據結構,它和咱們如今說的事件環有什麼關係麼?固然有,首先要明確的一點是,javascript代碼的執行全都在棧裏,不管是同步代碼仍是異步代碼,這個必定要清楚。
而代碼咱們大致上分爲了同步代碼和異步代碼,其實異步代碼還能夠再分爲兩類:宏任務和微任務。
先別管什麼是宏任務和微任務,每每這種高大上的術語不利於咱們理解,咱們先這麼認爲:宏,便是宏觀的、大的;微即微觀的、小的。
javascript是解釋型語言,它的執行過程是這樣的:
語言描述的費勁,不如看圖:
經過以上的步驟能夠看到,不管是同步仍是異步,只要是執行的時候都是要在棧裏執行的,而一遍又一遍的回頭檢查異步隊列,這種執行方式 就是所謂的「事件環」。
明白了javascript的執行原理,咱們就不難理解以前的第二段代碼,爲何setTimeout爲0時會最後執行,由於setTimeout是異步代碼,必需要等全部的同步代碼都執行完,纔會執行異步隊列。即便setTimeout執行得再快,它也不可能在同步代碼以前執行。
聊了這麼多,咱們好像尚未說宏任務和微任務的話題呢,上面說了,異步任務又分爲微任務和宏任務,那它們又是一個怎樣的執行機制呢?
注意!微任務和宏任務的執行方式在瀏覽器和Node中有差別,有差別!重要的事咱們多說幾遍,如下咱們討論的是在瀏覽器的環境裏。
在瀏覽器的執行環境中,老是先執行小的、微任務,再執行大的、宏任務,回過頭再看看第三段代碼,爲何Promise的then方法在setTimeout以前執行?其根本原理就是由於Promise的then方法是一個微任務,而setTimeout是一個宏任務。
接下來咱們借用阮一峯老師的一張圖來講明:
其實,以上這張圖示咱們能夠再將它細化一點,這個圖上的異步隊列只畫了一個,也就是說沒有區分微任務隊列和宏任務隊列。咱們能夠腦補一下,在此圖上多加一個微任務隊列,當javascript執行時再多加一個判斷,若是是微任務就加到微任務隊列裏,宏任務就加到宏任務隊列裏,在清空隊列時,瀏覽器總會優先清空「微任務」。這樣就把瀏覽器的事件環撤底說全了。
最後來一個大考,如下代碼的運行結果是什麼:
<script type="text/javascript"> setTimeout(function () { console.log(1); Promise.resolve().then(function () { console.log(2); }); }); setTimeout(function () { console.log(3); }); Promise.resolve().then(function () { console.log(4); }); console.log(5); </script>
將此代碼拷到chrome中跑一下,結果是:
5 4 1 2 3
不妨咱們試着分析一下爲何是這個結果,首先輸出5,由於console.log(5)
是同步代碼,這沒什麼可說的。
以後將前兩個setTimeout和最後一個Promise放入異步隊列,注意它們的區分,此時執行完了同步代碼以後發現微任務和宏任務隊列中都有代碼,按瀏覽器的事件環機制,優先執行微任務,此時輸出4。
而後執行宏任務隊列裏的第一個setTimeout,輸出1。
此時,setTimeout中又有一個Promise,放入微任務隊列。
再次清空微任務隊列,輸出2。
最後宏任務隊列裏還有最後一個setTimeout,輸出3。
而Node中的事件環又和瀏覽器有些許的不一樣,在node.js的官方文檔中有專門的描述,其中文檔中有一張圖,詳細的說明了它的事件環機制,咱們把它拿出來:
能夠看到,node.js中的事件環機制分爲了6個階段,其中最重要的3個階段我在上面作了註明:
圖中每個階段都表明了一個宏任務隊列,在Node事件環中,微任務的運行時機是在每個「宏任務隊列」清空以後,在進入下一個宏任務隊列之間執行。這是和瀏覽器的最大區別。
仍是用代碼說話吧,有一道經典的Node.js事件環面試題:
const fs = require('fs'); fs.readFile('./1.txt', (err, data) => { setTimeout(() => { console.log('timeout'); }); setImmediate(() => { console.log('immediate'); }); Promise.resolve().then(() => { console.log('Promise'); }); });
運行結果:
Promise immediate timeout
代碼並不複雜,首先使用fs模塊讀取了一個文件,在回調的內部有兩個宏任務和一個微任務,微任務老是優於宏任務執行的,所以先輸出Promise。
可是以後的區別爲何先輸出immdiate?緣由就在於fs讀取文件的宏任務在上圖中的第4個輪詢階段,當第4個階段清空隊列以後,就該進入第5個check階段,也就是setImmediate這個宏任務所在的階段,而不會跳回第1個階段,所以先輸出immedate。
最後總結一下,分析完瀏覽器和Node的事件環發現它們並不簡單,但只要記住了它們之間的區別就能夠分析出結果。
瀏覽器事件環是運行完一個宏任務立刻清空微任務隊列。
Node事件環是清空完一個階段的宏任務隊列以後再清空微任務隊列。
最後,總結一下常見的宏任務和微任務:
宏任務 | 微任務 |
---|---|
setTimeout | Promise的then方法 |
setInterval | process.nextTick |
setImmediate | MutationObserver |
MessageChannel |