本文為開發者總結了4個Node.js重點。
1. 非阻塞(Non-blocking)或非同步I/O
由於Node.js一個伺服器端框架,所以它主要工作之一是處理瀏覽器請求。在傳統的I/O系統中,每個請求的發出都是在上一請求到達之後才發出的。所以這被稱為阻塞(blocking)I/O。伺服器會阻擋其它的請求以處理當前請求,從而導致瀏覽器等待。
Node.js不以這種方式來進行I/O處理。如果一個請求需要長時間處理,Node.js會把該請求傳送給一個事件循環(event loop),然後繼續處理在呼叫堆疊(call stack)中的下一請求。當延遲請求處理完畢時,它會告知Node.js同時瀏覽器會做出回應回饋。
以下使用一個事例來說明。
Blocking I/O
// take order for table 1 and wait... var order1 = orderBlocking(['Coke', 'Iced Tea']); // once order is ready, take order back to table. serveOrder(order1); // once order is delivered, move on to another table. // take order for table 2 and wait... var order2 = orderBlocking(['Coke', 'Water']); // once order is ready, take order back to table. serveOrder(order2); // once order is delivered, move on to another table. // take order for table 3 and wait... var order3 = orderBlocking(['Iced Tea', 'Water']); // once order is ready, take order back to table. serveOrder(order3); // once order is delivered, move on to another table.
在這個餐廳例子中,服務生接收了菜單指令,等待飯菜處理,然後在飯菜處理完成後把飯菜端到桌子上。在服務生等待餐點處理期間,他會拒絕其它客人的菜單指示。
Non-blocking I/O
// take order for table 1 and move on... orderNonBlocking(['Coke', 'Iced Tea'], function(drinks){ return serveOrder(drinks); }); // take order for table 2 and move on... orderNonBlocking(['Beer', 'Whiskey'], function(drinks){ return serveOrder(drinks); }); // take order for table 3 and move on... orderNonBlocking(['Hamburger', 'Pizza'], function(food){ return serveOrder(food); });
而在非阻塞模式下,服務生會告知廚師他接受到的選單指令,然後去接收下一張桌子的指令。當第一桌飯菜處理完畢時,他會為那桌客人上菜,然後繼續接收其它客人的指示。這樣一來服務生不會因為阻塞指令而造成時間浪費。
2. 原型(Prototype)
原型在JS中是一個複雜的概念。在典型繼承機制語言如Java或C++中,為了實作程式碼重複使用,你必須先建立一個類別然後透過它來產生物件或透過類別擴充來產生物件。但是在JS中沒有類似的類別概念。在JS中建立一個物件後,你需要透過它來擴展物件或建立新物件。這就叫做原型繼承(prototypal inheritence)。
每個JS物件都連接著一個原型物件並對並繼承該物件的屬性。每個物件與預定義JS的Object.prototype相聯繫。如果你透過obj.propName或obj['propName'>方式來查找物件屬性但查找失敗時,這時可嘗試透過obj.hasOwnProperty('propName')的方式進行查找,JS運行時會在在原型對象中查找屬性。如果屬性不存在於原型鏈中,那麼將會傳回undefined值。
讓我們用以下範例來說明:
if (typeof Object.create !== 'function') { Object.create = function (o) { var F = function () {}; F.prototype = o; return new F(); }; var otherPerson = Object.create(person);
當你建立了一個新對象,你必須選取一個以原型為基礎的物件。在這裡,我們為物件函數新增了一個create方法。 create方法創建了一個以其它對象為原型的對象,並作為參數傳入。
當我們改變更新的物件時,它的原型是保持不變的。但是,當我們改變了原型對象,該變更會影響所有基於該原型的物件。
3. 組件(Modules)
如果你曾在Java中使用過包,那麼Node.js的元件與之類似。如果沒有,也不用擔心;組件其實是簡單的JS文件,用來實現特定的功能。組件模式的意義是讓你工作得更輕鬆。要使用元件,你必須像在JAVA中導入套件一樣進行JS檔案導入。 Node.js中有兩種元件
核心元件(Core Modules)- 核心元件是結合Node.js函式庫預先編譯的。其目的是把程式設計師常使用的功能開放出來,避免重複勞動。常見的核心元件有HTTP, URL, EVENTS, FILE SYSTEM等等。
使用者自訂元件(UserDefined Modules)- 使用者自訂元件是提供給使用者使用以實現特定功能的元件。當核心組件不足以滿足程式設計師需求的時候,自訂元件就可派上用場了。
元件是透過require函數被抽取的。如果這是一個核心元件,那麼參數就是元件名稱。如果這是一個使用者自訂元件,那麼參數就是其在檔案系統中的元件路徑。例如:
// extract a core module like this var http = require('http); // extract a user defined module like this var something = require('./folder1/folder2/folder3/something.js');
4. 回调(Callbacks)
在JS中,函数是第一类对象。也就是说你可以像对常规对象那样对函数进行所有操作。例如指派函数到一个变量,把这些作为参数传给方法,把它们声明为对象的属性,甚至是把它们从函数里返回。
回调在JS中是异步函数,可以作为参数传递给其它函数或从其它函数里执行或返回而后再执行。这是回调的基本概念。
当我们把一个回调函数作为参数传递给另外的函数时,我们传递的仅仅是函数的定义;换言之,我们不会知道回调函数的执行时间。这完全依赖于回调函数机制。它会在稍后某个时间点进行回调调用。这是Node.js的非阻塞或异步行为的基本概念,可用下例进行说明:
setTimeout(function() { console.log("world"); }, 2000) console.log("hello");
这是一个最简单的调用。我们把一个匿名函数作为参数进行传递,作用是为setTimeout函数进行控制台的输出记录登记。因为这仅仅是个函数定义,我们不知道函数何时会被执行。这取决于setTimeout函数的second参数,即2S后。
首先,second记录语句记录了对控制台的输出,2S后,在回调函数中的记录语句记录了输出的内容。
// output hello world
写在最后
以上4点对Node.js开发者来说是要彻底理解和掌握的,建议多动手来好好体会这4个要点的含义。