培育能力的事必須繼續不斷地去作,又必須隨時改善學習方法,提升學習效率,纔會成功。 —— 葉聖陶javascript
Node的首要目標是提供一種簡單的,用於建立高性能服務器的開發工具。還要解決web服務器高併發的用戶請求。java
咱們這裏來舉個例子,咱們node和java相比,在一樣的請求下誰更佔優一點。看圖node
併發
性能,它能夠快速經過主線程綁定事件。java每次都要建立一個線程,雖然java如今有個線程池
的概念,能夠控制線程的複用和數量。(單線程)
,致使其下面的時間沒法快速綁定,因此node不適用於大型密集型CPU運算案例
,而java卻很適合。web端場景主要是用戶的請求
或者讀取靜態資源
什麼的,很適合node開發。應用場景主要有聊天服務器
,電子商務網站
等等這些高併發的應用。web
Node.js是一個基於 Chrome V8 引擎的JavaScript運行環境(runtime)
,Node不是一門語言,是讓js運行在後端的運行時
,而且不包括javascript全集,由於在服務端中不包含DOM
和BOM
,Node也提供了一些新的模塊例如http,fs
模塊等。Node.js 使用了事件驅動、非阻塞式 I/O
的模型,使其輕量又高效而且Node.js 的包管理器 npm
,是全球最大的開源庫生態系統。面試
總而言之,言而總之,它只是一個運行時,一個運行環境。數據庫
(回調函數)
。非阻塞式i/o
,便可以異步讀寫。事件驅動
(發佈訂閱)。
進程
是操做系統分配資源和調度任務的基本單位,線程
是創建在進程上的一次程序運行單位,一個進程上能夠有多個線程。npm
在此以前咱們先來看看瀏覽器的進程機制後端
自上而下,分別是:promise
從咱們的角度來看,咱們更關心的是瀏覽器的
渲染引擎
,讓咱們往下看。瀏覽器
多線程
的,包含ui線程和js線程。ui線程和js線程會互斥,由於js線程的運行結果會影響ui線程,ui更新會被保存在隊列,直到js線程空閒,則被取出來更新。這裏我先要說一下瀏覽器的事件環,可能有人會說,你這篇文章明明是講node的怎麼會扯到瀏覽器。首先他們都是以js爲底層語言的不一樣運行時,有其類似之處,再者多學一點也不怕面試官多問。好了我廢話很少說,開始。
隊列是先進先出的
,好比下面的圖,最早進隊列的會先被打出去引用變量
是指向堆裏的引用對象的地址,只是一串地址。這裏棧表明的是執行棧,咱們js的主線程。棧是先進後出的
,先進後出就是至關於喝水的水杯,咱們倒水進去,理論上喝到的水是最後進水杯的。咱們能夠看代碼,follow me。function a(){ console.log('a') function b(){ console.log('b') function c(){ console.log('c') } c() } b() } a() //這段代碼是輸出a,b,c,執行棧中的順序的c,b,a,若是是遵循先進先出,就是輸出c,b,a。因此棧先進後出這個特性你們要牢記。 複製代碼
OK,如今你們已經知道堆,棧和隊列的關係,如今咱們來看一張圖。
我分析一下這張圖
setTimeout、onClick
等等的一些操做,咱們會將他的執行結果放入隊列,此期間主線程不阻塞event loop
在隊列裏面從頭開始取,在執行棧中執行event loop
永遠不會斷Event Loop
(事件循環機制)macro-task(宏任務): setTimeout,setImmediate,MessageChannel micro-task(微任務): 原生Promise(有些實現的promise將then方法放到了宏任務中),Object.observe(已廢棄), MutationObserver
微任務和宏任務皆爲異步任務,它們都屬於一個隊列,主要區別在於他們的執行順序,Event Loop的走向和取值。那麼他們之間到底有什麼區別呢
每次執行棧的同步任務執行完畢,就會去任務隊列中取出完成的異步任務,隊列中又分爲microtasks queues和宏任務隊列
等到把microtasks queues全部的microtasks
都執行完畢,注意是全部的
,他纔會從宏任務隊列
中取事件。等到把隊列中的事件取出一個
,放入執行棧執行完成,就算一次循環結束,以後event loop
還會繼續循環,他會再去microtasks queues
執行全部的任務,而後再從宏任務隊列
裏面取一個
,如此反覆循環。
microtasks
,把全部microtasks queues
清空macrotasks queues
的完成事件,在執行棧執行microtasks
我這麼說可能你們會有點懵,不慌,咱們來看一道題
setTimeout(()=>{ console.log('setTimeout1') },0) let p = new Promise((resolve,reject)=>{ console.log('Promise1') resolve() }) p.then(()=>{ console.log('Promise2') }) 複製代碼
最後輸出結果是Promise1,Promise2,setTimeout1
microtasks
,會在同步任務執行完後會去清空microtasks queues
,Promise.resolve().then(()=>{ console.log('Promise1') setTimeout(()=>{ console.log('setTimeout2') },0) }) setTimeout(()=>{ console.log('setTimeout1') Promise.resolve().then(()=>{ console.log('Promise2') }) },0) 複製代碼
這回是嵌套,你們能夠看看,最後輸出結果是Promise1,setTimeout1,Promise2,setTimeout2
microtasks queues
找microtasks queues
,輸出Promise1,同時會生成一個異步任務setTimeout1宏任務隊列
查看此時隊列是setTimeout1在setTimeout2以前,由於setTimeout1執行棧一開始的時候就開始異步執行,因此輸出setTimeout1,在執行setTimeout1時會生成Promise2的一個microtasks,放入microtasks queues
中microtasks queues
,輸出Promise2microtasks queues
,就又會去宏任務隊列取一個,這回取的是setTimeout2node的事件環相比瀏覽器就不同了,咱們先來看一張圖,他的工做流程
(APPLICATION)
會先進入v8引擎,v8引擎中主要是一些setTimeout
之類的方法。require('fs').read()
,node就會交給libuv
庫處理,這個libuv
庫是別人寫的,他就是node的事件環。libuv
庫是經過單線程異步的方式來處理事件,咱們能夠看到work threads
是個多線程的隊列,經過外面event loop
阻塞的方式來進行異步調用。work threads
隊列中有執行完成的事件,就會經過EXECUTE CALLBACK
回調給EVENT QUEUE
隊列,把它放入隊列中。EVENT QUEUE
隊列的事件,交給咱們的應用node中的event loop是在libuv裏面的,libuv裏面有個事件環機制,他會在啓動node時,初始化事件環
event loop
執行到某個階段時,都會執行對應的事件隊列中的事件,依次執行event loop
就會執行下一個階段event loop
切換一個執行隊列時,就會去清空microtasks queues
,而後再切換到下個隊列去執行,如此反覆這裏咱們要注意setImmediate
是屬於check隊列的,還有poll隊列主要是異步的I/O操做,好比node中的fs.readFile()
咱們來具體看一下他的用法吧
setImmediate(()=>{ console.log('setImmediate1') setTimeout(()=>{ console.log('setTimeout1') },0) }) setTimeout(()=>{ console.log('setTimeout2') process.nextTick(()=>{console.log('nextTick1')}) setImmediate(()=>{ console.log('setImmediate2') }) },0) 複製代碼
setImmediate1
,此時event loop
在check隊列setImmediate1
從隊列取出以後,輸出setImmediate1
,而後會將setTimeout1
執行event loop
執行完check隊列以後,開始往下移動,接下來執行的是timers隊列setTimeout1
設置延遲爲0的話,其實仍是有4ms的延遲,那麼這裏就會有兩種狀況。先說第一種,此時setTimeout1
已經執行完畢
setTimeout2,setTimeout1
setTimeout2,setTimeout1
,在取出setTimeout2
時,會將一個process.nextTick
執行(執行完了就會被放入微任務隊列),再將一個setImmediate
執行(執行完了就會被放入check隊列)event loop
會再去尋找下個事件隊列,此時event loop
會發現微任務隊列有事件process.nextTick
,就會去清空它,輸出nextTick1
event loop
找到下個有事件的隊列check隊列,執行setImmediate
,輸出setImmediate2
setTimeout1
還未執行完畢(4ms耽誤了它的終身大事?)
event loop
找到timers隊列,取出*timers隊列**中的setTimeout2
,輸出setTimeout2
,把process.nextTick
執行,再把setImmediate
執行event loop
須要去找下一個事件隊列,這裏你們要注意一下,這裏會發生2步操做,一、setTimeout1
執行完了,放入timers隊列。二、找到微任務隊列清空。,因此此時會先輸出nextTick1
event loop
會找到check隊列,取出裏面已經執行完的setImmediate2
event loop
找到timers隊列,取出執行完的setTimeout1
。這種狀況下event loop
比上面要多切換一次因此有兩種答案
setImmediate1,setTimeout2,setTimeout1,nextTick1,setImmediate2
setImmediate1,setTimeout2,nextTick1,setImmediate2,setTimeout1
這裏的圖只參考了第一種狀況,另外一種狀況也相似
有些人可能會搞亂他們之間的關係,同步、異步
是被調用者的狀態,阻塞、非阻塞
是調用者的狀態、消息
接下來咱們來看看他們的組合會是怎麼樣的
組合 | 意義 |
---|---|
同步阻塞 | 這就至關於我去飯店吃飯,我須要在廚房等待菜燒好了,才能吃。我是調用者我須要等待上菜因而被阻塞,菜是被調用者作好直接給我是同步 |
異步阻塞 | 我去飯店吃飯,我須要等待菜燒好了才能吃,可是廚師有事,但願以後處理完事能作好以後通知我去拿,我做爲調用者等待就是阻塞的,而菜做爲被調用者是作完以後通知個人,因此是異步的,這種方式通常沒用。 |
同步非阻塞 | 我去飯店吃飯,先叫了碗熱菜,在廚房等廚師作菜,但我很餓,就開始吃廚房冷菜,我是調用者我沒等熱菜好就開始吃冷菜,是非阻塞的,菜做爲被調用者作好直接給我是同步的,這種方式通常也沒人用 |
異步非阻塞 | 我去飯店吃飯。叫了碗熱菜,廚師在作菜,但我很餓,先吃冷菜,廚師作好了通知我去拿,我是調用者我不會等熱菜燒好了再吃冷菜,是非阻塞的,菜做爲被調用者通知我拿是異步的 |
但願你們看了本篇文章都有收穫,這樣出去面試的時候就不會這樣
而是這樣。好了,最後但願你們世界盃都可以 逢賭必贏,本身喜歡的球隊也可以 殺進決賽。