進入正文以前,首先要感謝各位大佬對本人第一篇掘金文章《ES6版Promise實現,給你不同的體驗》的確定及指正,可能寫的不盡人意,可是大家的點贊會是我繼續分享的動力之一,只要努力過,結果就不要太在乎,由於努力以後的結果會讓你滿意!與諸位共勉!vue
好了,話很少說,接下來讓咱們進入今天的話題。今天咱們來談一談事件環究竟是什麼?javaScript
的事件環和Node
的事件環有什麼區別?有沒有一種無從下手的感受,別捉急,只要你仔細閱讀本篇文章,相信可以解開心中的疑惑。 java
俗話說,工欲善其事必先利其器。在進入瀏覽器事件環和Node事件環情節以前呢,咱們有必要了解如下幾組常見的概念。node
堆棧是在計算機領域不可忽視的概念,若是想要詳細瞭解,請移步《堆棧_百度百科》。在javaScript中,棧中存的是基本數據類型,會自動分配內存空間,自動釋放;堆中存的是引用數據類型,是動態分配的內存,大小不定也不會自動釋放。git
heap
堆:也能夠叫堆內存;是一種隊列優先,先進先出的數據結構;stack
棧:又名'堆棧',也是一種數據結構,不過它是按照先進後出原則存儲數據的。
嘻嘻😝,本身花了半天時間(誇張)畫得,自我感受良好。github
既然咱們大體理解了堆和棧的含義,咱們來看一道面試題,如何用js代碼實現隊列和棧的功能呢?其實很簡單啦,就是數組最基本經常使用的增刪方法。面試
let arr = new Array();
arr.push(1);
arr.push(2);
arr.shift();
複製代碼
let arr = new Array();
arr.push(1);
arr.push(2);
arr.pop();
複製代碼
首先,咱們應該知道進程比線程要大。一個程序至少要有一個進程,一個進程至少要有一個線程。就拿咱們常常用的瀏覽器爲例吧,爲了更直觀一些,先看下這張圖片:ajax
因而可知,瀏覽器就是多進程的,當一個網頁崩潰時不會影響其餘網頁的正常運行。咱們主要了解下一下幾個方面:JavaScript
最大的特色就是單線程的,其實應該說其主線程是單線程的。爲何這麼說呢?你想一下,若是js是多線程的,咱們在頁面中這個線程要刪了那個元素(不順眼),另外一個線程呢我要保留那個元素(我罩着的),這樣豈不是就亂套了。這也是爲何JavaScript
執行同步代碼,異步代碼並不會阻塞代碼的運行。setTimeout
、瀏覽器事件、ajax
的回調函數)setTimeout
定時器所在線程)ajax
請求線程)宏任務和微任務能夠說都是異步任務。若是瞭解vue
源碼的同窗,應該知道宏任務macrotask
和微任務microtask
這兩個概念,他們的執行時機是不同的。vue
的$nextTick
的源碼就是經過宏任務和微任務實現的。(能夠去vue的github瞭解一下其實現原理)。數據庫
macrotask
有:setTimeout
、setInterval
、 setImmediate
(ie瀏覽器才支持,node中本身也實現了)、MessageChannel
microtask
有:promise.then()
、process.nextTick
(node的)瀏覽器中,事件環的運行機制是,先會執行棧中的內容,棧中的內容執行後執行微任務,微任務清空後再執行宏任務,先取出一個宏任務,再去執行微任務,而後在取宏任務清微任務這樣不停的循環,咱們能夠看下面這張圖理解一下:數組
從圖中能夠看出,同步任務會進入執行棧,而異步任務會進入任務隊列(callback queue)等待執行。一旦執行棧中的內容執行完畢,就會讀取任務隊列中等待的任務放入執行棧開始執行。(圖中缺乏微任務)promise
那麼,咱們來道面試題檢驗一下,當咱們在瀏覽器中運行下面的代碼,輸出的結果是什麼呢?
setTimeout(() => {
console.log('setTimeout1');
Promise.resolve().then(data => {
console.log('then3');
});
},1000);
Promise.resolve().then(data => {
console.log('then1');
});
Promise.resolve().then(data => {
console.log('then2');
setTimeout(() => {
console.log('setTimeout2');
},1000);
});
console.log(2);
// 輸出結果:2 then1 then2 setTimeout1 then3 setTimeout2
複製代碼
- 先執行棧中的內容,也就是同步代碼,因此2被輸出出來;
- 而後清空微任務,因此依次輸出的是
then1
then2
;- 因代碼是從上到下執行的,因此1s後
setTimeout1
被執行輸出;- 接着再次清空微任務,
then3
被輸出;- 最後執行輸出
setTimeout2
Node
是基於V8引擎的JavaScript
運行環境,在處理高併發、I/O密集(文件操做、網絡操做、數據庫操做等)場景有明顯的優點。Node的事件環機制與瀏覽器的是不太同樣。 在Node運行環境中:
libuv
處理libuv
經過阻塞I/O和多線程實現異步I/O其實本質是在libuv
(一個高性能的,事件驅動的I/O庫)內部有這樣一個事件環機制。在Node啓動時會初始化事件環,話很少說,先上圖:
event loop
執行到某個階段時會將當前階段對應的隊列依次執行。當隊列執行完畢或者執行數量超過上限時,纔會轉入下一個階段。node中的微任務在切換隊列時執行。
timers
計時器:執行setTimeout
、setInterval
的回調函數;I/O callbacks
:執行I/O callback
被延遲到下一階段執行;idle, prepare
:隊列的移動,僅內部使用poll
輪詢:檢索新的I/O事件;執行I/O相關的回調check
:執行setImmediate
回調close callbacks
:執行close
事件的callback
,例如socket.on("close",func)
好了,接下來咱們先來看一道簡單的測試題:
setTimeout(function () {
console.log('setTimeout');
});
setImmediate(function () {
console.log('setImmediate');
});
複製代碼
這道題中若是你在
node
環境中多運行幾回,就會發現輸出順序是不固定的。也就是說雖然上圖中timers
隊列在check
隊列前面,可是setTimeout
和setImmediate
沒有明確的前後順序的,這是由node
的準備時間(準備工做會浪費必定的時間)致使的。
對應上面這道練習題,咱們再來看下面這道題:
let fs = require('fs');
fs.readFile('./1.txt', function () {
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
});
});
複製代碼
這道題的輸出順序是
setImmediate
而後setTimeout
,不管你運行多少次,結果順序不會發生改變。這是由於fs
文件操做(I/O操做)屬於屬於poll
階段,poll
階段的下一階段就是check
階段,因此輸出順序是毋庸置疑的。
最後,讓咱們來再看一道面試題加深對Node事件環的理解:
setImmediate(() => {
console.log('setImmediate1');
setTimeout(() => {
console.log('setTimeout1')
}, 0);
});
Promise.resolve().then(res=>{
console.log('then');
})
setTimeout(() => {
process.nextTick(() => {
console.log('nextTick');
});
console.log('setTimeout2');
setImmediate(() => {
console.log('setImmediate2');
});
}, 0);
複製代碼
這道題的輸出順序是:
then、setTimeout二、nextTick、setImmediate一、setImmediate二、setTimeout1
,爲何是這樣的順序呢?微任務nextTick
的輸出是由於timers
隊列切換到check
隊列,setImmediate1
和setImmediate2
連續輸出是因只有當前隊列執行完畢後才能進去下一對列。
總結:今天的話題就先到這裏了。可能本文有表述不清楚或者理解不對的地方,還請各位大佬給予指正,我會繼續努力每週分享,萬分感謝!若是您以爲看了這篇文章有所收穫,請不要忘了動動手指點個小❤️哦!讓咱們一塊兒蕩起雙槳,天天進步一點點,在技術的道路上越走越遠!
原創不易,轉載請註明出處!謝謝!