打開node官網(wǎng),可以看到首頁正中寫著:
Node.js? is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
其中:event-driven —— 事件驅(qū)動;non-blocking I/O —— 非阻塞I/O,就是說的node的特點。
實際上,我所理解的node的特點有三個:單線程、事件驅(qū)動、非阻塞I/O,這三者是三個特點,但理解起來都是一個意思。
單線程
像java、PHP等這樣的后端語言,都是多線程的,即當(dāng)有一個請求過來的時候,開啟一個CPU,它使計算機(jī)能夠在同一時間執(zhí)行多個線程。而node的單線程是指當(dāng)遇到需要加載數(shù)據(jù)庫、讀取磁盤等請求的時候,它會將其放入“隊列”中執(zhí)行,待下一輪事件循環(huán)的時候再判斷能否執(zhí)行它的回調(diào)函數(shù),若此時它的回調(diào)函數(shù)需要加載I/O則放入“隊列”中,它的特點是線程利用率是100%。
事件驅(qū)動
舉一個通俗點的例子,你在餐廳吃飯,如果當(dāng)時店內(nèi)生意比較好,你坐下來,服務(wù)員過來招待你,這時,另一桌也剛坐下并呼叫服務(wù)員。正常情況下,服務(wù)員肯定會想給你個菜單讓你自己看看,看好了再叫他,接著去招呼那一桌的客人了,完了再給你端茶什么的。
這就是事件驅(qū)動。通過監(jiān)聽事件的狀態(tài)變化來做出相應(yīng)的操作。當(dāng)你發(fā)出一個請求的時候,如果這個請求需要等待,那這個請求便會被放入“隊列”中,在處理這個請求的同時,后續(xù)的無需請求也在被處理,事件處理結(jié)束后,調(diào)用請求的回調(diào)函數(shù)。注:在處理無需等待的事件時,事件循環(huán)是暫停的。
非阻塞I/O
阻塞I/O就是當(dāng)用戶發(fā)一個讀取文件描述符的操作的時候,進(jìn)程就會被阻塞,直到要讀取的數(shù)據(jù)全部準(zhǔn)備好返回給用戶。那非阻塞I/O呢,就與上面的情況相反,用戶發(fā)起一個讀取文件描述符操作的時,函數(shù)立即返回,不作任何等待,進(jìn)程繼續(xù)執(zhí)行。但是程序如何知道要讀取的數(shù)據(jù)已經(jīng)準(zhǔn)備好了呢?最簡單的方法就是輪詢,即事件循環(huán)。
好了,文章開始我說這三個特點就是一個特點,是因為總結(jié)起來,它們可以用下面這個圖表示。

剛啟動node文件,第一輪循環(huán),處理無需等待的事件,需要讀取磁盤、網(wǎng)絡(luò)通信的事件會被放入“隊列”,待它們處理完畢且事件循環(huán)到時,會處理它們的回調(diào)函數(shù),如果回調(diào)函數(shù)需等待,則再放入“隊列”,再循環(huán)。
node中,只有I/O操作是異步的,對于大量計算,它仍然會被阻塞,所以node不適合有大量計算的工作。