菜鳥的Node.js之旅

flowerwxc發表於2015-11-26

凌晨三點,手在鍵盤上,盯著空空如也的控制檯。明亮的提示符在黑色背景下待命,期待著輸入。想折騰一會兒 Node.js 嗎?Node.js 的一大優點是可以在任何地方執行。這就帶來了眾多的實驗性玩法。對任何一個老程式設計師而言,命令列工具都充滿樂趣。我最喜歡的一點是,可以先從帶有安全措施的命令列開始嘗試。更酷的是我們談論的其實還是 JavaScript,大多數讀者對此應該不會感到陌生。現在就在控制檯把 node 跑起來吧!

本文介紹 Node.js 主要目的是帶讀者領略一下 Node 最有趣的部分,同時對某些地方進行深入探討。本文是中等難度的Node.js技術棧概覽,所有內容都限制在控制檯之內。如果你在找的是方便初學者上手的 Node.js 教程,我建議你看一下 SitePoint 提供的付費教程:Node.js 入門 。

為什麼選擇 Node.js?

首先,羅列一下讓 Node.js 脫穎而出的幾個要點。 •是非阻塞 I/O 設計 •是非同步操作設計 •可以執行 Chrome V8 JavaScript

你或許已經從多種渠道獲悉了以上幾點,但是它們都是什麼意思呢?可以把 Node.js 當作是對 JavaScript 開放了眾多 API 的引擎。在傳統的計算過程是同步的,也就是說進行 I/O 操作的時候,API 在執行下一行程式碼之前要等待。這裡說的 I/O 操作可以是讀取檔案或者進行網路訪問。而 Node.js 沒有選擇這樣做,而是從設計之初就使用非同步操作。現如今這是巨大的優勢。還記得上一次是什麼時候因為更快的單核處理器買了一臺新電腦嗎?多核和高速硬碟驅動器才是更重要的。

下文中的 > 代表命令列提示符,也就是說需要按下Enter鍵來輸入下一個命令。還要記得先開啟終端輸入 node 命令再執行文中的程式碼。那麼讓我們開始Node.js之旅吧。

回撥函式

首先,輸入以下函式: 1.> function add(a, b, callback) { var result = a + b; callback(result); }

對新手而言,JavaScript 的回撥函式看上去或許有點奇怪。它當然看上去不像傳統的物件導向程式設計的方法。對於 JavaScript 來說,函式也是物件,而物件是可以接受其他物件作為引數的。JavaScript 不關心物件有什麼,所以函式可以接受一個物件,而該物件可以是另外一個函式。引數的數量可以像 add() 一樣有兩個,也可以像回撥函式只有一個。回撥函式系統功能強大,因其使得封裝和隱藏實施細節成為可能。許多 Node.js 的 API 都將回撥函式作為引數。還可以將回撥函式看作是一種「代理」。繞過程式設計術語不談,「代理」就是被派遣並被授權代表其他人的人。這麼看來,回撥函式就像派人跑腿。只要給定一個引數列表,比如一個購物清單,「代理」就可以自行完成任務。

再嘗試一下add: 1.> add(2, 3, function (c) { console.log('2 + 3 = ' + c) }); 2.> add(1, 1, function (c) { console.log('Is 1 + 1 = 3? ' + (c === 3)); });

你可以隨便試試用更具創造性的方式使用回撥函式。回撥函式是一些重要的 Node.js API 的基石。

非同步操作

有了回撥函式就可以構建非同步 API 了。舉個例子: 1.> function doSomething (asyncCallback) { asyncCallback(); } 2.> doSomething(function () { console.log('This runs synchronously.'); });

這個特殊的例子可以同步執行。但是卻有了在 JavaScript 實現非同步操作的全部要素。比如說 asyncCallback ,就可以在同一個執行緒中延遲。 1.> function doSomething (asyncCallback) { setTimeout(asyncCallback, Math.random() + 1000); } 2.> doSomething(function () { console.log('This runs asynchronously.'); }); console.log('test');

使用 setTimeout 在當前執行緒實現延遲執行。超時不能保證執行的時間。加入 Math.random() 可以使之更隨機。隨後呼叫 doSomething() ,再用 console.log('test') 顯示延遲的執行。你會感受到大概一到兩秒鐘的短暫延時,隨後會有訊息顯示在螢幕上。這個例子說明,非同步回撥是不可預測的。Node.js 將回撥函式交給排程程式之後就不再管它,然後自己該幹嘛幹嘛去了。一旦計時器到時間了,Node.js 就會回到執行發生的地方並呼叫回撥函式。所以只有想方設法地理解了回撥函式才能理解 Node.js。

總之,Node.js 的回撥函式並不總是和 JavaScript 一致。

再來看一些更酷的例子。如何用 Node.js 實現一個簡單的 DNS 查詢? 1.> dns.lookup('bing.com', function (err, address, family) { console.log(' Address: ' + address + ', Family: ' + family + ', Err: ' + err); });

回撥函式的返回了 err,address 和 family 物件。重點是返回值像引數一樣被傳給了回撥函式。因此,這與 var result = fn('bing.com'); 這種傳統的 API 是不同的。對於 Node.js,要著眼大局就必須使用回撥函式和非同步操作。請隨意查閱DNS Node.js API[2] 文件瞭解更多細節。下面是我在控制檯上實現的 DNS 查詢的結果。

檔案I/O

趕緊再看一個例子,如何用 Node.js 讀寫檔案呢?想象下面這個場景,你開啟一個檔案,讀取內容再寫一些東西進去。在現代計算機體系結構中,I/O 密集型操作相當滯後。CPU 暫存器,CPU 快取和 RAM 的讀取速度都很快。讀取磁碟的速度卻很慢。所以同步程式執行 I/O 密集型操作的時候會非常慢。此時最好使用非同步,下面就是一個例子: 1.> var fs = require('fs'); 2.> fs.writeFile('message.txt', 'Hello Node.js', function () { console.log('Saved.'); }); console.log('Writing file...');

因為事實上操作是非同步的,在檔案儲存在磁碟之前你會看到“寫入檔案......“。這個 API 中自然地使用回撥函式是非常合適的。你可以查閱檔案系統API[6] 文件獲取更多資訊。那麼如何讀取檔案呢?你可以不假思索地猜出如何用 Node.js 實現嗎?一個提示,回撥函式的引數是 err 和 data 。自己先試著解一下這個問題。

下面給出答案: 1.> fs.readFile('message.txt', function(err, data) { console.log(data); });

還可以通過傳入 encoding 選項來獲得 utf-8 編碼的檔案的內容: 1.> fs.readFile('message.txt', {encoding: 'utf-8'}, function(err, data) { console.log(data); });

在 Node.js 中結合非同步 I/O 使用回撥函式看上去不錯吧。這樣做的好處在於改善了 JavaScript 的一個基本組成部分。有了非阻塞的非同步 API ,回撥函式得以提升到令人驚歎的新高度。

Web 伺服器

那麼怎樣實現 Web 伺服器呢?任何出色的 Node.js 示範總得通過執行 Web 伺服器得以體現。假設有一個名為 createServer 的 API ,它有一個回撥函式,而函式接受兩個引數:request 和 response 。你可以研究一下HTTP的API文件。能想出個大概嗎?這時你需要的是 http 模組。開始吧,在控制檯上嘗試一下。

下面是答案: 1.> var http = require('http'); 2.> var server = http.createServer(function (request, response) { response.end('Hello Node.js'); });

當你在考慮 web 的時候,其實是在想一個可以發起請求並做出應答的客戶端-伺服器模型。Node.js 具有來自客戶端的 request 物件和來自伺服器的 response 物件。所以「Node.js技術棧」用這種簡單的回撥機制欣然吸收了 web 的關鍵所在。我還需要提醒你這是非同步的嗎?我希望你已經能夠拼湊出一個整體的印象了。如果回顧一下檔案 API ,那麼這裡的東西沒有兩樣。依舊是引入一個模組,交待它要去做什麼,然後將其傳給回撥函式。回撥函式就跟代理一樣,會根據給定的引數獵豹執行指定的任務。

當然,如果我們不能看到它在瀏覽器上正常執行的話一切都是扯蛋。在命令列上輸入: 1.server.listen(8080);

然後用你喜歡的瀏覽器去訪問 localhost:8080 就好了,我用的是 Edge 瀏覽器,如下圖所示:

不妨將 request 看作是可以給你提供海量資訊的物件。若是要重新連線的 server ,先將其關閉: 1.> server.close(); 2.> server = http.createServer(function (request, response) { response.end(request.headers['user-agent']); }); server.listen(8081);

用瀏覽器訪問 localhost:8081。headers 物件會提供從瀏覽器得來的 user-agent 資訊。還可以迴圈訪問 headers 物件: 1.> server.close(); 2.> server = http.createServer(function (request, response) { Object.keys(request.headers).forEach(function (key) { response.write(key + ': ' + request.headers[key] + ' '); }); response.end(); }); server.listen(8082);

此時還是用瀏覽器訪問 localhost:8082。嘗試完伺服器操作之後記得將其關閉。不然命令列可能會出現一些奇怪的問題。 1.> server.close();

這樣就搞定了,我們完全用命令列就構建了 Web 伺服器。希望你喜歡這個圍繞著 node 的奇幻之旅。

結論

Node.js 簡單輕巧,非常適合用作現代化的解決方案。它通過無阻塞設計充分利用了現代化的硬體。它還包含了 Web 固有的客戶端-伺服器模型。最重要的是它執行的是我們喜愛的 JavaScript 。最吸引我的是,Node.js「技術棧」的核心都不是什麼新玩意。Web 誕生之初就是圍繞著輕量級可訪問模組構建的。我建議你抽空閱讀一下蒂姆·伯納斯的設計原則這篇文章。文中的最少能量原則對 Node.js 來說就是選擇採用 JavaScript。

希望你喜歡本文的命令列工具之旅,祝你 happy hacking.

原文連結:http://www.gbtags.com/gb/share/9493.htm

相關文章