作者簡介:五月君,Software Designer,公眾號「Nodejs技術棧」作者。
Node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行時。在 2020 年 10 月 27 日 Node.js v14.15.0 LTS 版已發布,即長期支持版本,其中包含了很多很棒的新功能,以下內容也是基於筆者在日常 Node.js 工作和學習中所總結的,可能不全,同時也歡迎補充,有些功能之前也曾單獨寫過文章來介紹,接下讓我們一起看看都有哪些新的變化?
如果我們使用 JavaScript 不管是用在前端或者 Node.js 服務端都會出現如下情況,因為我們有時是不確定 user 對象是否存在,又或者 user 對象裡面的 address 是否存在,如果不這樣判斷, 可能會得到類似於 Cannot read property 'xxx' of undefined 這樣的類似錯誤。
const user = {
name: 'Tom',
address: {
city: 'ZhengZhou'
}
}
if (user && user.address) {
console.log(user.address.city)
}現在我們有一種優雅的寫法 "可選鏈操作符",不必明確的驗證鏈中的每個引用是否有效,以符號 "?." 表示,在引用為 null 或 undefined 時不會報錯,會發生短路返回 undefined。
user.address?.city
user.address?.city?.length
// 結合 ?.[] 的方式訪問相當於 user.address['city']
user.address?.['city']
// 結合 delete 語句使用,僅在 user.address.city 存在才刪除
delete user.address?.city參考 v8.dev/features/optional-chaining[1]
Nullish Coalescing(空值合併)邏輯或操作符(||)會在左側為假值時返回右側的操作符,例如我們傳入一個屬性為 enabled:0 我們期望輸出左側的值,則是不行的。
function Component(props) {
const enable = props.enabled || true; // true
}
Component({ enabled: 0 })現在我們可以使用 **空值合併操作符(??)**來實現,僅當左側為 undefined 或 null 時才返回右側的值。
function Component(props) {
const enable = props.enabled ?? true; // 0
}
Component({ enabled: 0 })參考:v8.dev/features/nullish-coalescing[2]
Intl.DisplayNames對於國際化應用需要用到的語言、區域、貨幣、腳本的名稱,現在 JavaScript 開發者可以使用 Intl.DisplayNames API 直接訪問這些翻譯,使應用程式更輕鬆的顯示本地化名稱。
Language(語言)let longLanguageNames = new Intl.DisplayNames(['zh-CN'], { type: 'language' });
longLanguageNames.of('en-US'); // 美國英語
longLanguageNames.of('zh-CN'); // 中文(中國)
longLanguageNames = new Intl.DisplayNames(['en'], { type: 'language' });
longLanguageNames.of('en-US'); // American English
longLanguageNames.of('zh-CN'); // Chinese (China)
Region(區域)let regionNames = new Intl.DisplayNames(['zh-CN'], {type: 'region'});
regionNames.of('US'); // 美國
regionNames.of('419'); // 拉丁美洲
regionNames = new Intl.DisplayNames(['en'], {type: 'region'});
regionNames.of('US'); // United States
regionNames.of('419'); // Latin America
Currency(貨幣)let currencyNames = new Intl.DisplayNames(['zh-CN'], {type: 'currency'});
currencyNames.of('CNY'); // 人民幣
currencyNames.of('USD'); // 美元
currencyNames = new Intl.DisplayNames(['en'], {type: 'currency'});
currencyNames.of('CNY'); // Chinese Yuan
currencyNames.of('USD'); // US Dollar
Script(腳本)let scriptNames = new Intl.DisplayNames(['zh-CN'], {type: 'script'});
scriptNames.of('Hans'); // 簡體
scriptNames.of('Latn'); // 拉丁文
scriptNames = new Intl.DisplayNames(['en'], {type: 'script'});
scriptNames.of('Hans'); // Simplified
scriptNames.of('Latn'); // Latin參考:v8.dev/features/intl-displaynames[3] 上述實例用到的國家代號和 code 都可從參考地址獲取。
Intl.DateTimeFormatIntl.DateTimeFormat API 用來處理特定語言環境的日期格式。
const date = new Date();
// Sunday, January 10, 2021 at 9:02:29 PM GMT+8
new Intl.DateTimeFormat('en-US', { dateStyle: 'full', timeStyle: 'long'}).format(date)
// 21/1/10 中國標準時間 下午9:02:29.315
new Intl.DateTimeFormat('zh-CN', {
year: '2-digit',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
fractionalSecondDigits: 3,
timeZoneName: 'long'
}).format(date)參考: Intl/DateTimeFormat[4]
String.prototype.matchAllmatchAll() 返回一個包含所有匹配正則表達式的結果,返回值為一個不可重用(不可重用意思為讀取完之後需要再次獲取)的迭代器。
matchAll() 方法在 Node.js v12.4.0 以上版本已支持,該方法有個限制,如果設置的正則表達式沒有包含全局模式 g ,在 Node.js v14.5.0 之後的版本如果沒有提供會拋出一個 TypeError 異常。
// const regexp = RegExp('foo[a-z]*','g'); // 正確
const regexp = RegExp('foo[a-z]*'); // 錯誤,沒有加全局模式
const str = 'table football, foosball, fo';
const matches = str.matchAll(regexp); // TypeError: String.prototype.matchAll called with a non-global RegExp argument
for (const item of matches) {
console.log(item);
}參考:ES2020-features-String-prototype-matchAll-throws-on-non-global-regex[5]
AsyncLocalStorageNode.js Async Hooks 模塊提供了 API 用來追蹤 Node.js 程序中異步資源的聲明周期,在最新的 v14.x LTS 版本中新增加了一個 AsyncLocalStorage 類可以方便實現上下文本地存儲,在異步調用之間共享數據,對於實現日誌鏈路追蹤場景很有用。
下面是一個 HTTP 請求的簡單示例,模擬了異步處理,並且在日誌輸出時去追蹤存儲的 id。
const http = require('http');
const { AsyncLocalStorage } = require('async_hooks');
const asyncLocalStorage = new AsyncLocalStorage();
function logWithId(msg) {
const id = asyncLocalStorage.getStore();
console.log(`${id !== undefined ? id : '-'}:`, msg);
}
let idSeq = 0;
http.createServer((req, res) => {
asyncLocalStorage.run(idSeq++, () => {
logWithId('start');
setImmediate(() => {
logWithId('processing...');
setTimeout(() => {
logWithId('finish');
res.end();
}, 2000)
});
});
}).listen(8080);下面是運行結果,我在第一次調用之後直接調用了第二次,可以看到我們存儲的 id 信息與我們的日誌一起成功的列印了出來。
image.png便利性的同時也會犧牲一些性能上的代價,關於 AsyncLocalStorage 的詳細使用介紹參見筆者的另一篇文章 「在 Node.js 中使用 async hooks 模塊的 AsyncLocalStorage 類處理請求上下文」 中的介紹。
ES Modules 支持ES Modules 的支持總體上來說是個好事,進一步的規範了 Node.js 與瀏覽器的模塊生態,使之進一步趨同,同時避免了進一步的分裂。
在當前 Node.js v14.x LTS 版本中已移除試驗性支持,現在使用無需使用標誌了,它使用 import、export 關鍵字,兩種使用方式:
使用 .mjs 擴展名// caculator.mjs
export function add (a, b) {
return a + b;
};
// index.mjs
import { add } from './caculator.js';
console.log(add(4, 2)); // 6
告訴 Node.js 將 JavaScript 代碼視為 ES Modules默認情況下 Node.js 將 JavaScript 代碼視為 CommonJS 規範,所以我們要在上面使用擴展名為 .mjs 的方式來聲明,除此之外我們還可以在 package.json 文件中 設置 type 欄位為 module 或在運行 node 時加上標誌 --input-type=module 告訴 Node.js 將 JavaScript 代碼視為 ES Modules。
// package.json
{
"name": "esm-project",
"type": "module",
...
}前端的同學可能會對以上使用 ES Modules 的方式很熟悉。
詳細使用參見筆者在文章 「在 Nodejs 中 ES Modules 使用入門講解」 中的介紹。
Top-Level Await頂級 await 支持在異步函數之外使用 await 關鍵字,在 Node.js v14.x LTS 版本中已去掉試驗性支持,現在使用也不再需要設置標誌。
import fetch from 'node-fetch';
const res = await fetch(url)也可以像調用函數一樣動態的導入模塊。
const myModule = await import('./my-module.js');對於異步資源,之前我們必須在 async 函數內才可使用 await,這對一些在文件頂部需要實例化的資源可能會不好操作,現在有了頂級 await 我們可以方便的在文件頂部對這些異步資源做一些初始化操作。
詳細使用參見筆者在文章 「Nodejs v14.3.0 發布支持頂級 Await 和 REPL 增強功能」 中的介紹。
Diagnostic report(診斷報告)Diagnostic report 是 Node.js v14.x LTS 提供的一個穩定功能,在某些情況下會生成一個 JSON 格式的診斷報告,可用於開發、測試、生產環境。報告會提供有價值的信息,包括:JavaScript 和本機堆棧信息、堆統計信息、平臺信息、資源使用情況等,幫助用戶快速追蹤問題。
https://github.com/IBM/report-toolkit[6] 是 IBM 開發的一個款工具,用於簡化報告工具的使用,如下是一個簡單 Demo 它會造成服務的內存洩漏。
const total = [];
setInterval(function() {
total.push(new Array(20 * 1024 * 1024)); // 大內存佔用,不會被釋放
}, 1000)最終生成的 JSON 報告被 report-toolkit 工具診斷的結果可能是下面這樣的。
image.png詳細使用參見筆者在文章 「在 Node.js 中使用診斷報告快速追蹤問題」 中的介紹。
Stream新版本中包含了對 Stream 的一些更改,旨在提高 Stream API 的一致性,以消除歧義並簡化 Node.js 核心各個部分的行為,例如:
http.OutgoingMessage 與 stream.Writable 類似net.Socket 的行為與 stream.Duplex 完全相同一個顯著的變化 autoDestroy 的默認值為 true,使流在結束之後始終調用 _destroy參考:Node.js version 14 available now#Stream [7]
使用異步迭代器使用異步迭代器我們可以對 Node.js 中的事件、Stream 亦或者 MongoDB 返回數據遍歷,這是一件很有意思的事情,儘管它不是 Node.js v14.x 中新提出的功能,例如 event.on 是在 Node.js v12.16.0 才支持的,這些目前看到的介紹還不太多,因此我想在這裡做下簡單介紹。
在 Events 中使用Node.js v12.16.0 中新增了 events.on(emitter, eventName) 方法,返回一個迭代 eventName 事件的異步迭代器,例如啟動一個 Node.js 服務可以如下這樣寫,想知道它的原理的可以看筆者下面提到的相關文章介紹。
import { createServer as server } from 'http';
import { on } from 'events';
const ee = on(server().listen(3000), 'request');
for await (const [{ url }, res] of ee)
if (url === '/hello')
res.end('Hello Node.js!');
else
res.end('OK!');
在 Stream 中使用以往我們可以通過 on('data') 以事件監聽的方式讀取數據,通過異步迭代器可以一種更簡單的方式實現。
async function readText(readable) {
let data = '';
for await (const chunk of readable) {
data += chunk;
}
return data;
}目前在 JavaScript 中還沒有被默認設定 [Symbol.asyncIterator] 屬性的內建對象,在 Node.js 的一些模塊 Events、Stream 中是可使用的,另外你還可以用它來遍歷 MongoDB 的返回結果。
關於異步迭代器詳細使用參見筆者在文章 「探索異步迭代器在 Node.js 中的使用」 中的介紹。
參考資料[1]v8.dev/features/optional-chaining: https://v8.dev/features/optional-chaining
[2]v8.dev/features/nullish-coalescing: https://v8.dev/features/nullish-coalescing
[3]v8.dev/features/intl-displaynames: https://v8.dev/features/intl-displaynames
[4]Intl/DateTimeFormat: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat
[5]ES2020-features-String-prototype-matchAll-throws-on-non-global-regex: https://node.green/#ES2020-features-String-prototype-matchAll-throws-on-non-global-regex
[6]https://github.com/IBM/report-toolkit: https://github.com/IBM/report-toolkit
[7]Node.js version 14 available now#Stream : https://nodejs.medium.com/node-js-version-14-available-now-8170d384567e