[譯] Node.js 新特性將顛覆 AI、物聯網等更多驚人領域

Node.js 新特性將顛覆 AI、物聯網等更多驚人領域

新版 Node.js 的特性並非這個平臺此前的那些等閒賣點。Node.js 主要以其迅速和簡潔而聞名。這也是爲何那麼多公司都願意嘗試 Node.js。然而,隨着最新的 LTS(long-term support,長期支持)版本的發佈,Node.js 將會帶來很多讓每位 Node.js 開發者欣喜若狂的新特性。爲什麼?因爲 Node.js 12 新鮮出爐的特性及其帶來的可能性簡直讓人驚豔!

多線程趨向穩定!

在上一個 LTS 版本中,我們已經可以使用多線程了。誠然,這是一個試驗性特性,需要一個名爲 --experimental-worker 的標誌(flag)才能生效。

在即將問世的這個 LTS 版本(Node 12)中,多線程仍是試驗性的,但不再需要依賴 --experimental-worker 這種標誌了。穩定版本正在向我們翩躚走來!

支持 ES 模塊

我們需要認清這樣的事實:ES 模塊是目前 JavaScript 開發的必經之路。我們在前端應用中使用它。我們在桌面端乃至移動端應用中使用它。可是,在 Node.js 領域,我們還卡在 Common.js 模塊停滯不前。

當然了,我們還有 Babel 和 TypeScript 可以用,但既然 Node.js 是一門後端技術,我們應當關心的應該只是服務器上安裝的 Node 的版本是否更新。我們不必去在意五花八門的瀏覽器和 Node.js 對它們的支持情況,那麼安裝專門針對此目的而設計的工具(Babel、Webpack 等)有什麼意義呢?

在 Node 10 版本中,我們總算是可以用 ES 模塊小試牛刀了(目前的 LTS 版本對模塊進行了試驗性的實現),但還需要使用一個特定的文件擴展 —— .mjs(模塊 JavaScript 代碼文件)。

而在 Node 12 版本中,使用 ES 模塊要稍微容易一些了。正如在 Web App 中一樣,我們可以用一個專有的屬性類型來定義某段代碼是應該處理爲 Common.js 還是 ES 模塊。

要想把文件都作爲模塊使用,你只需在 package.json 中添加 type 屬性並賦值爲 module

{
  "type": "module"
}

從現在起,如果離 .js 文件最近的 package.json 文件帶有 type 屬性,那麼這些 .js 文件將作爲模塊存在。再見了您吶,mjs(如果想用,還是可以繼續用的)!

那麼,要是我們想要用 Common.js 風格的模塊,該怎麼辦呢?

只要離它最近的 package.json 不包含模塊屬性 type,那它就將被視爲是遵循 Common.js 規範的代碼。

另外,我們可以使用一種新型的擴展文件,叫做 cjs —— 代表一個 Common.js 文件。

每個 mjs 文件都是一個 ES 模塊,而每個 cjs 都是一個 Common.js 文件。

如果你還沒嘗過這一勺鮮,那現在趕緊試試吧!

JavaScript 和私有變量

說起 JavaScript,我們總需要絞盡腦汁防止類或函數中的數據外泄。

JavaScript 因其猴子補丁(Monkey patching)而聞名,這意味着我們總是能通過某種門路拿到所有數據。

我們嘗試過用閉包、Symbol 等等模擬私有變量。Node 12 版本裝載了新版 V8 引擎,因此我們有機會使用一個炫酷特性 —— 類中的私有屬性。

我想你們都還記得在 Node 中實現私有性的老方法:

class MyClass {
  constructor() {
    this._x = 10
  }
  
  get x() {
    return this._x
  }
}

我們都清楚,這並非真正的私有 —— 我們總有辦法拿到它,但大多數 IDE 都把它看作私有字段,多數 Node 開發者都知道這個慣例。現在,我們終於可以將這種方法拋之腦後了。

class MyClass {
  #x = 10
  
  get x() {
    return this.#x
  }
}

能看到二者的差異嗎?沒錯,我們用 # 告訴 Node,這個變量是私有變量,只能在類內部訪問到。

如果試圖直接訪問它,你會看到報錯信息,說這個變量不存在。

令人鬱悶的是,有些 IDE 目前還不能識別這種私有變量。

Flat 和 flatMap

在 Node 12 版本中,我們可以盡情使用 JavaScript 的新特性。

首先,我們可以使用數組的新方法 —— flatflatMap。前者很像 Lodash 中的 flattenDepth 方法。

如果向方法中傳入一個嵌套的數組,可以得到一個展開的數組。

[10, [20, 30], [40, 50, [60, 70]]].flat() // => [10, 20, 30, 40, 50, [60, 70]]
[10, [20, 30], [40, 50, [60, 70]]].flat(2) // => [10, 20, 30, 40, 50, 60, 70]

如你所見,該方法還有個特別的參數 —— depth(深度)。這個參數決定了嵌套數組將以何種深度被降維。

第二個新特性 —— flatMap,其作用類似於首先執行 map 方法,再執行 flat。🙂

可選的 Catch 綁定

另一個新特性就是 可選的 Catch 綁定(Optional catch binding)。此前,我們總是需要爲 try - catch 定義一個 error 變量。

try {
  someMethod()
} catch(err) {
  // err 變量是必須的
}

而在 Node 12 版本中,我們雖不能完全擺脫 try - catch 語句,但 error 變量是可以省了。

try {
  someMethod()
} catch {
  // err 變量是可選的
}

Object.fromEntries

還有一個新特性就是 Object.fromEntries 方法。其主要用途是通過 Map 或者鍵值對數組創建一個對象。

Object.fromEntries(new Map([['key', 'value'], ['otherKey', 'otherValue']]));
// { key: 'value', otherKey: 'otherValue' }


Object.fromEntries([['key', 'value'], ['otherKey', 'otherValue']]);
// { key: 'value', otherKey: 'otherValue' }

V8 引擎的變化

我提到過,新版的 Node 裝載了 V8 引擎。這使得 Node 不僅支持私有變量,還帶有一些性能優化功能。

Await 將會像 Javascript 解析那樣運行飛快。

而由於支持堆棧追蹤,我們的應用將會加載得更快,Async 代碼將更加易於調試。

另外,堆(Heap)的大小也正在改變。此前,其體量爲 700MB(在 32 位系統中)或 1400MB(在 64 位系統中)。隨着新版本帶來的變化,堆大小將依可用內存大小而定!

Node 12 版本來啦!

我不知道你期不期待,反正我是對 Node 12 拭目以待。距離官方將 12 版本更新爲 LTS 版本還要幾個月(發佈日期定於 2019 年 10 月),但我們將要得到的新特性無疑是前途無量的。

只有幾個月啦!

新版 Node.js 最大的看點就是多線程!

每種編程語言都各有其利弊,這是我們大家都毋庸置疑的。大多數流行的技術都在技術世界有各自的一席之地。Node.js 也不例外。

幾年來,我們一直都說 Node.js 適用於 API gateway 和實時儀表板(如基於 Websocket)。事實上,Node 的設計讓我們不得不依賴微服務架構來彌補其本身的常見缺陷。

經過時間的檢驗,我們已知悉,由於其單線程設計理念,Node.js 不適合處理耗時長、嚴重佔用 CPU 算力或阻塞操作的任務。這是事件循環機制本身的問題。

如果有一個複雜的同步操作阻塞了事件循環,那麼在該操作完成前,別的什麼也做不了。這就是我們頻繁使用 Async 或將耗時間的邏輯移到單獨的微服務中的原因。

隨着 Node.js 10 版本中的新特性的面世,這種權宜之計將變得不再必要。這個化腐朽爲神奇的工具就是 Worker thread。正因如此,Node.js 將能夠在通常我們會使用其他語言的領域中大放異彩。

人工智能、機器學習或大數據都是很好的佐證,就目前來說,這些領域的研究需要大量的 CPU 算力,這讓我們別無他選,只能搭建更多的服務或者換一個更適合的語言。但從新版 Node.js 開始,一切都不一樣了。

支持多線程?怎麼做到的?

這個新特性仍處在試驗階段 —— 還不能在生產環境中使用。但我們還是可以隨意玩玩的。那從哪開始呢?

從 Node 12 開始及至更高版本中,我們不再需要使用特定的特性標誌 --experimental-worker。 Worker 將是默認激活的!

node index.js

現在我們可以充分利用 worker_threads 模塊。讓我們先寫一個簡單的帶有兩個方法的 HTTP 服務器:

  • GET /hello(返回帶有“Hello World”信息的 JSON 對象),
  • GET /compute(使用一個同步方法重複加載一個大 JSON 文件)。
const express = require('express');
const fs = require('fs');

const app = express();

app.get('/hello', (req, res) => {
  res.json({
    message: 'Hello world!'
  })
});

app.get('/compute', (req, res) => {
  let json = {};
  for (let i=0;i<100;i++) {
    json = JSON.parse(fs.readFileSync('./big-file.json', 'utf8'));
  }

  json.data.sort((a, b) => a.index - b.index);

  res.json({
    message: 'done'
  })
});

app.listen(3000);

這段代碼的運行結果很容易預測。當 GET /compute/hello 被同時調用,我們必須等到 compute 調用完成才能從 hello 得到響應。事件循環被阻塞,直到文件加載完成。

讓我們用多線程優化一下吧!

const express = require('express');
const fs = require('fs');
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');

if (isMainThread) {
  console.log("Spawn http server");

  const app = express();

  app.get('/hello', (req, res) => {
    res.json({
      message: 'Hello world!'
    })
  });

  app.get('/compute', (req, res) => {

    const worker = new Worker(__filename, {workerData: null});
    worker.on('message', (msg) => {
      res.json({
        message: 'done'
      });
    })
    worker.on('error', console.error);
	  worker.on('exit', (code) => {
		if(code != 0)
          console.error(new Error(`Worker stopped with exit code ${code}`))
    });
  });

  app.listen(3000);
} else {
  let json = {};
  for (let i=0;i<100;i++) {
    json = JSON.parse(fs.readFileSync('./big-file.json', 'utf8'));
  }

  json.data.sort((a, b) => a.index - b.index);

  parentPort.postMessage({});
}

很明顯,這種語法和我們所知道的 Node.js 集羣擴展非常相似。但從這兒就開始變得有趣起來了。

你可以試着同時調用兩個路徑。注意到什麼了嗎?。沒錯,事件循環不再被阻塞,這樣我們就能在文件加載期間調用 /hello 了。

現在,這就是我們都翹首以盼的東西!剩下的就是等待穩定版本的 API 出爐了。

渴望更多的 Node.js 新特性?這個 N-API 能夠構建 C/C++ 模塊!

Node.js 的原生運行速度正是我們青睞這個技術的原因之一。Worker threads 將會更進一步地提升 Node.js 的速度。但僅僅是這樣就夠了嗎?

Node.js 是一種基於 C 語言的技術。當然了,我們把 JavaScript 當作一個主要編程語言來使用。但如果我們能用 C 語言做更加複雜的計算呢?

Node.js 10 版本給我們帶來了 N-API。這是一個標準化的 API,適用於原生模塊,讓用 C/C++ 甚至是 Rust 語言構建模塊成爲可能。聽起來很棒,對吧?

用 C/C++ 構建 Node.js 原生模塊變得更加容易。

下面是一個很簡單的原生模塊示例:

#include <napi.h>
#include <math.h>

namespace helloworld {
    Napi::Value Method(const Napi::CallbackInfo& info) {
        Napi::Env env = info.Env();
        return Napi::String::New(env, "hello world");
    }

    Napi::Object Init(Napi::Env env, Napi::Object exports) {
        exports.Set(Napi::String::New(env, "hello"),
                    Napi::Function::New(env, Method));
        return exports;
    }

    NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
}

如果你有 C++ 的基礎,寫一個自定義模塊肯定不費吹灰之力。你只需記得在模塊結尾將 C++ 的類型轉化爲 Node.js 類型即可。

接下來我們需要綁定(binding):

{
    "targets": [
        {
            "target_name": "helloworld",
            "sources": [ "hello-world.cpp"],
            "include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
            "dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
            "defines": [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ]
        }
    ]
}

這個簡單的配置讓我們能夠構建 *.cpp 文件,以便於後續在 Node.js 應用中使用。

在用於 JavaScript 代碼之前,我們必須進行構建並配置 package.json 文件來查找 gyp 文件(綁定文件)。

{
  "name": "n-api-example",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "install": "node-gyp rebuild"
  },
  "gypfile": true,
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "node-addon-api": "^1.5.0",
    "node-gyp": "^3.8.0"
  }
}

當模塊準備就緒,我們就可以用 node-gyp rebuild 命令進行構建並導入到 JavaScript 代碼中。用法和其他流行的模塊的用法一樣!

const addon = require('./build/Release/helloworld.node');

console.log(addon.hello());

N-API 以及 Worker threads 賦予我們功能強大的工具,幫我們構建高性能的應用。不用說 API 或儀表板 —— 即使是複雜的數據處理或者機器學習系統都將垂手可得。多麼棒啊!

另請參閱:Swoole – Is it Node in PHP?

Node.js 會全面支持 HTTP/2 嗎?當然了!何樂不爲?

我們能夠計算得更快。我們能進行分佈式計算。那麼資源和頁面服務方面表現如何?

多年來,我們一直都卡在優秀卻陳舊的 http 模塊和 HTTP/1.1 上沒有進步。隨着服務器要提供的資源越來越多,我們越來越受制於加載所花費的時間。針對每個服務器或代理服務器,各個瀏覽器都有個併發持久連接數上限,特別是 HTTP/1.1 協議下。有了對 HTTP/2 的支持,我們就可以和這個問題吻別了。

那我們該從哪裏下手呢?你是否還記得網上每個教程中都會出現的這個 Node.js 基礎示例?對,就是這個:

const http = require('http');

http.createServer(function (req, res) {
  res.write('Hello World!');
  res.end();
}).listen(3000);

在 Node.js 10 版本中,有一個嶄新的 http2 模塊可以讓我們使用 HTTP/2.0!可算是迎來了 HTTP/2.0!

const http = require('http2');
const fs = require('fs');

const options = {
  key: fs.readFileSync('example.key'),
  cert: fs.readFileSync('example.crt')
 };

http.createSecureServer(options, function (req, res) {
  res.write('Hello World!');
  res.end();
}).listen(3000);

我們心心念唸的就是 Node.js 10 版本中全面支持的 HTTP/2。

這些新特性會讓 Node.js 的未來一片光明

Node.js 的新特性爲我們的技術生態注入了新鮮血液。它們給 Node.js 插上翅膀,讓它飛向新的天地。你想到過這個技術有一天會用於圖像識別或者數據科學嗎?我也從來沒有想到過。

這個版本的 Node.js 還帶來了更多的人們期盼已久的特性,例如對 ES 模塊的支持(雖然仍出於試驗階段);又如 fs 方法的更新,終於讓我們能夠脫離回調地獄、擁抱 Promise 天堂了。

想知道更多的 Node.js 新特性嗎?請觀看這個短視頻

我們可以發現,經過歷年的增長,Node.js 的人氣在 2017 年早期達到了巔峯。這並不是增長開始緩慢的跡象,而是標誌着這個技術的成熟。

不論如何,我能夠清晰地看出,所有這些新的改進和 Node.js 區塊鏈應用(基於 truffle.js 框架)的走紅,或可進一步推動 Node.js 的發展,讓 Node.js 在新型的項目、角色和環境中梅開二度。

TSH(The Software House)Node.js 團隊非常期待 2020 年的到來!

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久鏈接 即爲本文在 GitHub 上的 MarkDown 鏈接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章