[譯] 閱讀 NodeJS 文檔,我學到了這 19 件事情

  • 原文地址:19 things I learnt reading the NodeJS docs
  • 原文作者:David Gilbertson
  • 譯文出自:掘金翻譯計劃
  • 譯者:jacksonke20120711@gmail.com
  • 校對者:mortyu, rottenpen)

我相信我對 Node 瞭若指掌。我這 3 年來寫的網站都是用 Node 來開發的。但實際上,我從沒有詳細查看 Node 文檔。

長期的訂閱者應該知道,我正處在書寫每一個介面(interface),屬性(prop),方法(method),函數(function),數據類型(data type)等等關於 Web 開發的漫漫長途中,這樣可以填補我的知識面的空缺。在完成了 HTML,DOM, WebApi, CSS, SVG 和 EcmaScript 之後, Node 文檔會是我的最後一站。

對我來說,這裡面有很多寶貴的知識,所以我想簡短地列舉,並且分享它們。我會按吸引力從高到低列舉它們,好比我見新朋友時的衣服順序,(最吸引人的放外面 ^_^)

把 querystring 當作通用解析器

假設你從一些古怪的資料庫中獲取到的數據是一些鍵值對數組,格式像name:Sophie;shape:fox;condition:new。很自然的,你會將它當成一個 JavaScript 對象。你會將所取得的數據以;為分隔符切分成數組,然後遍曆數組,用:分割,第一項作為屬性,第二項作為該屬性對應的值。

這樣對吧?

不用這般麻煩的,你可以使用 querystring

const weirdoString = `name:Sophie;shape:fox;condition:new`;nconst result = querystring.parse(weirdoString, `;`, `:`);nn// result:n// {n// name: `Sophie`,n// shape: `fox`,n// condition: `new`,n// };n

Query String | Node.js v7.0.0 Documentation_By default, percent-encoded characters within the query string will be assumed to use UTF-8 encoding. If an alternative…http://_nodejs.org

V8 Inspector

運行 node,加上--inspect選項,會給出一個 URL 地址。粘貼該 URL 到 Chrome。哈哈,這就能用 Chrome DevTools 調試 Node,這多方便,多輕鬆。這篇文章有介紹如何使用 how-to by Paul Irish over here .

雖然它現在還處於「試驗」階段,但是現在已經極大地解決了我的困撓。

Debugger | Node.js v7.0.0 Documentation_Node.js includes a full-featured out-of-process debugging utility accessible via a simple TCP-based protocol and built…http://_nodejs.org

nextTick 和 setImmediate 的不同點

和多數情況一樣,如果能給它們起個更貼切的名字,就很容易記住兩者的不同了。

process.nextTick() 是 process.sendThisToTheStartOfTheQueue().(譯者註:放入隊列的第一個位置)

setImmediate() 應該被叫做 sendThisToTheEndOfTheQueue().(譯者註:放入隊列的尾部,最後一個處理的)

(題外話:React 中,我通常將props當成stuffThatShouldStayTheSameIfTheUserRefreshes,而將state當成stuffThatShouldBeForgottenIfTheUserRefreshes.這兩者長度一致也是個意外,哈哈哈。)

Node.js v7.0.0 Documentation_Stability: 3?—?Locked The timer module exposes a global API for scheduling functions to be called at some future period…http://_nodejs.org

process | Node.js v7.0.0 Documentation_A process warning is similar to an error in that it describes exceptional conditions that are being brought to the user…http://_nodejs.org

Node v0.10.0 (Stable)_I am pleased to announce a new stable version of Node. This branch brings significant improvements to many areas, with…http://_nodejs.org

Server.listen 只帶一個參數對象

對於參數傳遞,我傾向於只使用一個參數 options ,而不是傳 5 個沒命名且必須按照特定順序的參數。這可以在服務端監聽連接時使用。

require(`http`)n .createServer()n .listen({n port: 8080,n host: `localhost`,n })n .on(`request`, (req, res) => {n res.end(`Hello World!`);n });n

這個文檔比較隱蔽,它並不在http.Server的方法列表裡,而是在net.Server中(http.Server繼承net.Server)

net | Node.js v7.0.0 Documentation_Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the…http://_nodejs.org

相對路徑

傳入fs模塊方法的路徑可以是相對路徑。這是相對於process.cwd()。這可能多數人都知道了,但我以前一直以為要傳入絕對路徑。

const fs = require(`fs`);nconst path = require(`path`);nn// why have I always done this...nfs.readFile(path.join(__dirname, `myFile.txt`), (err, data) => {n // do somethingn});nn// when I could just do this?nfs.readFile(`./path/to/myFile.txt`, (err, data) => {n // do somethingn});n

File System | Node.js v7.0.0 Documentation_birthtime 「Birth Time」?—?Time of file creation. Set once when the file is created. On filesystems where birthtime is…http://_nodejs.org

路徑解析

以前我會顯擺的技術之一就是使用正則表達式從路徑字元串中獲取文件名和拓展名,這其實根本沒有必要,需要做的僅僅是調用介面:

myFilePath = `/someDir/someFile.json`;npath.parse(myFilePath).base === `someFile.json`; // truenpath.parse(myFilePath).name === `someFile`; // truenpath.parse(myFilePath).ext === `.json`; // truen

Node.js v7.0.0 Documentation_Stability: 2?—?Stable The path module provides utilities for working with file and directory paths. It can be accessed…http://_nodejs.org

使用不同顏色來記錄日誌

使用console.dir(obj, {colors: true})可以使用預先設置好的配色方案列印日誌,這樣更易於閱讀。

Console | Node.js v7.0.0 Documentation_The console functions are usually asynchronous unless the destination is a file. Disks are fast and operating systems…http://_nodejs.org

讓 setInterval() 不去影響應用的效率

假設你使用setInterval()來執行資料庫清理操作,一天一次。默認情況下,只要setInterval()的請求還在, Node 的事件循環是不會停止的。如果你想讓 Node 休息(我也不知道這樣做的好處),你可以這麼做:

const dailyCleanup = setInterval(() => {n cleanup();n}, 1000 * 60 * 60 * 24);nndailyCleanup.unref();n

需要注意的是,如果你的隊列中沒有其它的請求(比如 http 服務監聽),Node 會退出的。

Node.js v7.0.0 Documentation_Stability: 3?—?Locked The timer module exposes a global API for scheduling functions to be called at some future period…http://_nodejs.org

使用 Signal 常量

可能你以前會這樣處理 kill:

process.kill(process.pid, `SIGTERM`);n

如果計算機編程的歷史不存在由錯字引發的錯誤,這樣做沒什麼錯的。但是實際上這是發生過的。第二個參數可以是帶上』string』或者對應的 int ,你可以使用下面更健壯的方式

process.kill(process.pid, os.constants.signals.SIGTERM);n

IP 地址有效性驗證

Node 已經有內置的 IP 地址校驗器。我以前不止一次自己寫正則表達式去做這個。好蠢(┬_┬)

require(net).isIP(10.0.0.1) will return 4.

require(net).isIP(cats) will return 0.

因為cats並不是一個IP地址

如果你沒注意到,我正經歷著這麼個階段,字元串使用反引號包起來, 它在我身上越來越多,但我知道它看起來很奇怪,所以我特意提到它。。。(作者的嘮叨)

net | Node.js v7.0.0 Documentation_Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the…http://_nodejs.org

os.EOL

你曾經對行結束符硬編碼嗎?

我的天!

os.EOL是專門為你準備的,它在 Windows 操作系統上為rn,在其它系統上是n。使用 os.EOL 能讓你的代碼在不同的操作系統上表現一致。

const fs = require(`fs`);nn// badnfs.readFile(`./myFile.txt`, `utf8`, (err, data) => {n data.split(`rn`).forEach(line => {n // do somethingn });n});nn// goodnconst os = require(`os`);nfs.readFile(`./myFile.txt`, `utf8`, (err, data) => {n data.split(os.EOL).forEach(line => {n // do somethingn });n});n

OS | Node.js v7.0.0 Documentation_{ model: 『Intel(R) Core(TM) i7 CPU 860 @ 2.80GHz』, speed: 2926, times: { user: 252020, nice: 0, sys: 30340, idle…http://_nodejs.org

狀態碼查詢

HTTP 狀態碼及其對應的易讀性的名字是可以查詢的。http.STATUS_CODES正是我這裡想說的,它的鍵是個狀態碼,值對應其狀態的簡短描述。

所以你可以這麼做:

someResponse.code === 301; // truenrequire(`http`).STATUS_CODES[someResponse.code] === `Moved Permanently`; // truen

HTTP | Node.js v7.0.0 Documentation_The HTTP interfaces in Node.js are designed to support many features of the protocol which have been traditionally…http://_nodejs.org

預防崩潰

我一直認為下面的這種錯誤導致的服務崩潰是非常荒謬的:

const jsonData = getDataFromSomeApi(); // But oh no, bad data!nconst data = JSON.parse(jsonData); // Loud crashing noise.n

預防這種可笑的錯誤,你可以在你 app 的中使用process.on(uncaughtException, console.error);

當然,我不是傻瓜,在付費的項目中,我會使用 PM2 ,同時把所有的東西都裝到try...catch語句中。但是,私人免費項目就另說 o_o ….

警告,這個並非最好的練習,在大點複雜點的 app 中,這甚至可能是個壞主意。這需要你來決定是否要信任一個傢伙的博客文章或官方文檔。

process | Node.js v7.0.0 Documentation_A process warning is similar to an error in that it describes exceptional conditions that are being brought to the user…http://_nodejs.org

Just this once()

對所有的事件發送者(EventEmitters),除了on()方法之外,還有once(),我很確認我是地球上最後一個學到這點的人 (T_T)

server.once(`request`, (req, res) => res.end(`No more from me.`));n

Events | Node.js v7.0.0 Documentation_Much of the Node.js core API is built around an idiomatic asynchronous event-driven architecture in which certain kinds…http://_nodejs.org

定製控制台

你可以使用 new console.Console(standardOut, errorOut) 創建你自己的控制台,傳入你自己的輸出流。

為什麼要定製控制台? 我也不知道。或許想要將一些內容輸出到文件,套接字,或者其他東西的時候,會考慮定製控制台。

Console | Node.js v7.0.0 Documentation_The console functions are usually asynchronous unless the destination is a file. Disks are fast and operating systems…http://_nodejs.org

DNS查詢結果

Node 不緩存 DNS 返回的結果.所以當你一次又一次地查詢同一個 URL 的時候,其實已經浪費了很多寶貴的時間。這種情況下,你完全可以自己調用dns.lookup()並緩存結果的。或者可以這麼做,這個是先前有人實現的。

dns.lookup(`http://www.myApi.com`, 4, (err, address) => {n cacheThisForLater(address);n});n

DNS | Node.js v7.0.0 Documentation_2) Functions that connect to an actual DNS server to perform name resolution, and that always use the network to…http://_nodejs.org

fs模塊是多操作系統兼容性的雷區

如果你寫代碼的風格和我一樣—閱讀最少的知識,微調程序,直到它可以運行。那麼,你很有可能也會觸到fs模塊的雷區。雖然 Node 為多操作系統的兼容性做了很多,但畢竟也只能做到那麼多。許多 OS 的不同特性就像代碼海洋中突起的珊瑚瞧,每個瞧石都隱藏著風險。而你,僅僅是小船。

不幸的是,這些不同點不僅僅是存在於 Windows 和其它操作系統之間,所以,你不能簡單的自我安慰「哇,太好了,沒人使用 Windows」。(我寫過一大篇反對使用 Windows 來進行 Web 開發的文章,但我自己把它刪了,因為那些說教,連我自己看了都翻白眼)。

下面這些是你在使用fs模塊時,可能碰到的坑

  • fs.stats()返回的mode屬性在 Windows 和其它操作系統上是不同的(在 Windows 上沒有匹配一些文件模式常量,比如 fs.constants.S_IRWXU)
  • fs.lchmod()只能在 macOS 中使用
  • fs.symlink() 的type參數只可能在 Windows 上使用
  • fs.watch() 選項recursive只能在 macOS 和 Windows 中使用。
  • fs.watch() 在 Windows 和 Linux 上,回調只會接受一個文件名
  • fs.open() 打開一個文件夾,在 FreeBSD 和 Windows 上使用a+屬性是可以的,但是在 macOS 和 Linux 上是不行的。
  • fs.write() 在linux上,當文件是以append的方式打開的,參數position是會被直接忽視掉的,直接在文件末尾添加。

(我還算挺趕時髦的,我已經改用macOS了,OS X只用了 49 天)

File System | Node.js v7.0.0 Documentation_birthtime 「Birth Time」 – Time of file creation. Set once when the file is created. On filesystems where birthtime is…http://_nodejs.org

net 模塊是 http 模塊速度的兩倍

閱讀文檔,我學到了net模塊是個事兒。它支撐著http模塊。這會讓我思索,假如我只想做伺服器間的通訊 (server-to-server communication ),我是不是只需要使用net模塊?

網上的人或許很難相信我不能憑直覺獲得答案。作為一個 Web 開發者,我一開始就扎進了服務端的世界裡,我知道 http 但是其他方面並不是很多。所有的 TCP, 套接字,流之類的對我來說就像日本搖滾.我真的不是很明白,但是我很好奇。

為了比較驗證我的想法,我建立了多個服務端程序,(我相信這時你肯定在聽日本搖滾了),並且發送了多個請求。結論是 http.Server每秒中處理了大約3,400個請求,net.Server每秒鐘處理5,500個。

它其實也很簡單。

如果你感興趣的話,可以查看我的代碼。如果不感興趣,那不好意思,需要你滾動頁面了。

// This makes two connections, one to a tcp server, one to an http server (both in server.js)n// It fires off a bunch of connections and times the responsenn// Both send strings.nnconst net = require(`net`);nconst http = require(`http`);nnfunction parseIncomingMessage(res) {n return new Promise((resolve) => {n let data = ``;nn res.on(`data`, (chunk) => {n data += chunk;n });nn res.on(`end`, () => resolve(data));n });n}nnconst testLimit = 5000;nn/* ------------------ */n/* -- NET client -- */n/* ------------------ */nfunction testNetClient() {n const netTest = {n startTime: process.hrtime(),n responseCount: 0,n testCount: 0,n payloadData: {n type: `millipede`,n feet: 100,n test: 0,n },n };nn function handleSocketConnect() {n netTest.payloadData.test++;n netTest.payloadData.feet++;nn const payload = JSON.stringify(netTest.payloadData);nn this.end(payload, `utf8`);n }nn function handleSocketData() {n netTest.responseCount++;nn if (netTest.responseCount === testLimit) {n const hrDiff = process.hrtime(netTest.startTime);n const elapsedTime = hrDiff[0] * 1e3 + hrDiff[1] / 1e6;n const requestsPerSecond = (testLimit / (elapsedTime / 1000)).toLocaleString();nn console.info(`net.Server handled an average of ${requestsPerSecond} requests per second.`);n }n }nn while (netTest.testCount {n httpTest.responseCount++;nn if (httpTest.responseCount === testLimit) {n const hrDiff = process.hrtime(httpTest.startTime);n const elapsedTime = hrDiff[0] * 1e3 + hrDiff[1] / 1e6;n const requestsPerSecond = (testLimit / (elapsedTime / 1000)).toLocaleString();nn console.info(`http.Server handled an average of ${requestsPerSecond} requests per second.`);n }n });n }nn while (httpTest.testCount {n console.info(`Starting testNetClient()`);n testNetClient();n}, 50);nnsetTimeout(() => {n console.info(`Starting testHttpClient()`);n testHttpClient();n}, 2000);nn// This sets up two servers. A TCP and an HTTP one.n// For each response, it parses the received string as JSON, converts that object and returns a stringnconst net = require(`net`);nconst http = require(`http`);nnfunction renderAnimalString(jsonString) {n const data = JSON.parse(jsonString);n return `${data.test}: your are a ${data.type} and you have ${data.feet} feet.`;n}nn/* ------------------ */n/* -- NET server -- */n/* ------------------ */nnnetn .createServer((socket) => {n socket.on(`data`, (jsonString) => {n socket.end(renderAnimalString(jsonString));n });n })n .listen(8888);nn/* ------------------- */n/* -- HTTP server -- */n/* ------------------- */nnfunction parseIncomingMessage(res) {n return new Promise((resolve) => {n let data = ``;nn res.on(`data`, (chunk) => {n data += chunk;n });nn res.on(`end`, () => resolve(data));n });n}nnhttpn .createServer()n .listen(8080)n .on(`request`, (req, res) => {n parseIncomingMessage(req).then((jsonString) => {n res.end(renderAnimalString(jsonString));n });n });n

net | Node.js v7.0.0 Documentation_Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the…http://_nodejs.org

REPL技巧

  1. 當你處於 REPL(那是你在控制台敲入node,並按了回車鍵的情形),你可以敲入.load someFile.js,這時,它會將這個文件的內容載入進來。(比如,你可以載入一個包含大量常量的文件)。
  2. 當你設置環境變數NODE_REPL_HISTORY="",這樣可以禁止 repl 的歷史寫入文件中。同時我也學到(至少是被提醒了)REPL 的歷史默認是寫到~/.node_repl_history中,當你想回憶起之前的 REPL 歷史時,可以上這兒查。
  3. _ 這個變數,保存著上一次的計算結果. 相當方便!
  4. 當你進入 REPL 模式中時,模塊都已經為你載入好了。所以了,比如說,你可以直接敲入os.arch()查看操作系統體系結構。你不需要先敲入require(os).arch(); (注: 確的說,是按需載入的模塊.)

插播一條廣告:12 月 17 日,由 W3C 主辦的中國第三屆 CSS 開發者大會在天虹錦都酒店舉辦。此次大會,將邀請行業內知名講師,與大家共聚廣州,暢聊 CSS。大會邀請的嘉賓來自阿里、微信、Adobe Typekit 的資深前端工程師,來和 Mathias Bynens、Wenting Zhang、大漠、陳劍鑫、勾三股四等一起探討 CSS 那些事。感興趣的小夥伴快來報名參加吧!

推薦閱讀:

【偽教程】手把手教你成為matlab作曲家
我從編程總結的 22 個經驗
CMD有哪些有趣的命令?
【philippica】一次linux 下stack overflow實驗

TAG:Nodejs | 后端技术 | 编程 |