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

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

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

多線程趨向穩定!

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

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

支持 ES 模塊

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

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

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

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

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

{
  "type": "module"
}
複製代碼

從如今起,若是離 .js 文件最近的 package.json 文件帶有 type 屬性,那麼這些 .js 文件將做爲模塊存在。再見了您吶,mjs(若是想用,仍是能夠繼續用的)!express

那麼,要是咱們想要用 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++ logo

用 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);
複製代碼

Http/2 協議 logo

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

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

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

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

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

在下面的折線圖中,咱們能夠發現,通過歷年的增加,Node.js 的人氣在 2017 年早期達到了巔峯。這並非增加開始緩慢的跡象,而是標誌着這個技術的成熟。

Node.js 歷年人氣折線圖,2017 年達到峯值

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

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

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


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

相關文章
相關標籤/搜索