本文基於Nodejs v13.1.0html
閱讀本篇文章以前,請閱讀前置文章:node
閱讀完本篇文章以後,但願你能夠掌握如下知識點:linux
本文demo地址:傳送門git
誠如從暴力到 NAN 再到 NAPI——Node.js 原生模塊開發方式變遷一文所提到的NAN和NAPI的歷史,NAN爲了搞定」封建時代「混亂的C++原生模塊,再也不讓一個模塊只能被若干個nodejs版本使用,而提出使用宏定義來解決這個問題,因此說NAN是一大堆宏定義,兼容各類nodejs版本的宏定義。作到了一次編寫,處處編譯
。github
而這種設計模式仍是依然有缺點,那就是屢次編譯,也就是說你寫的插件若是到了更高的Nodejs版本,仍是須要再次編譯,讓宏定義再次匹配新的版本去編譯出新的插件包,因而在node v8版本以後,nodejs提出了新的一套機制,也就是咱們此次的主角-NAPI。面試
不一樣版本的 Node.js 使用一樣的接口,這些接口是穩定地 ABI 化的,即應用二進制接口(Application Binary Interface)。這使得在不一樣 Node.js 下,只要 ABI 的版本號一致,編譯好的 C++ 擴展就能夠直接使用,而不須要從新編譯。
複製代碼
那麼咱們怎麼查看當前Node版本的ABI版本呢?經過process.versions.modules
能夠打印出當前的ABI版本,nodejs提供了一份完整的ABI版本列表:算法
process.versions提供了Nodejs一些版本相關的提示,包括v8使用的版本,各個依賴包的版本,好比在版本v13.2.0下打印:(注:napi這個字段是NAPI模塊版本,它有一個本身的版本矩陣:N-API Version Matrix)json
{
node: '13.2.0',
v8: '7.9.317.23-node.20',
uv: '1.33.1',
zlib: '1.2.11',
brotli: '1.0.7',
ares: '1.15.0',
modules: '79',
nghttp2: '1.40.0',
napi: '5',
llhttp: '1.1.4',
openssl: '1.1.1d',
cldr: '35.1',
icu: '64.2',
tz: '2019c',
unicode: '12.1'
}
複製代碼
N-API 定義了以下特性:windows
node_api.h
;napi_status
枚舉,來表示此次調用成功與否;napi_status
佔坑了,因此真實返回值經過形參來傳遞;JavaScript
數據類型都被黑盒類型napi_value
封裝,再也不是相似於 v8::Object
、v8::Number
等類型;napi_get_last_error_info
函數來獲取最後一次出錯的信息。modules
字段,而是自定義了napi
這個字段?這個問題是留給你們思考的。有疑問歡迎留言討論。
老規矩,以下圖所示:
圖中咱們還發現有下面這麼一個結論:爲啥咱們直接require
JSON文件的時候,能夠自動轉化爲一個對象?
由於模塊解析的時候,若是是json後綴的時候,會調用JSON.parse
這個方法
Tips:上圖還能夠做爲面試題《請說說在Nodejs中require一個文件後一個簡單流程》的一個簡單答案
咱們重點關注到v8
裏面的DLOpen
方法。
該方法是爲了解析node
後綴的模塊而寫,node
模塊本質是一個動態連接庫(windows下後綴是dll,linux下後綴是so),因此你看v8源碼下,若是是支持__POSIX__標準的話,是使用系統APIdlopen()
打開便可,若是是非__POSIX__的話,就只能藉助libuv的uv_dlopen
方法去打開。
其次文件打開以後,執行如下幾個判斷:
最後執行模塊的初始化函數
這裏能夠看到N-API的模塊是不須要判斷的,從這裏也印證了這句話:
A given version n of N-API will be available in the major version of Node.js in which it was published, and in all subsequent versions of Node.js, including subsequent major versions.
複製代碼
利用nvm
切換nodejs版本,咱們先使用node v11.15.0編譯,再使用node v13.2.0執行,nodejs會報以下錯誤:
:16
internal/modules/cjs/loader.js:1190
return process.dlopen(module, path.toNamespacedPath(filename));
^
Error: The module '/Users/linxiaowu/Github/nodejs-NAPI-demo/packages/md5-NAN/build/Release/md5-nan.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 67. This version of Node.js requires
NODE_MODULE_VERSION 79. Please try re-compiling or re-installing
the module (for instance, using `npm rebuild` or `npm install`).
at Object.Module._extensions..node (internal/modules/cjs/loader.js:1190:18)
at Module.load (internal/modules/cjs/loader.js:976:32)
at Function.Module._load (internal/modules/cjs/loader.js:884:14)
at Module.require (internal/modules/cjs/loader.js:1016:19)
at require (internal/modules/cjs/helpers.js:69:18)
at Object.<anonymous> (/Users/linxiaowu/Github/nodejs-NAPI-demo/packages/md5-NAN/index.js:1:15)
at Module._compile (internal/modules/cjs/loader.js:1121:30)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1160:10)
at Module.load (internal/modules/cjs/loader.js:976:32)
at Function.Module._load (internal/modules/cjs/loader.js:884:14)
複製代碼
一樣使用上述的步驟,是能夠正常執行NAPI模塊的。其中v11.15.0的process.versions
:
{ node: '11.15.0',
v8: '7.0.276.38-node.19',
uv: '1.27.0',
zlib: '1.2.11',
brotli: '1.0.7',
ares: '1.15.0',
modules: '67',
nghttp2: '1.37.0',
napi: '4',
llhttp: '1.1.1',
http_parser: '2.8.0',
openssl: '1.1.1b',
cldr: '34.0',
icu: '63.1',
tz: '2018e',
unicode: '11.0' }
複製代碼
而v13.2.0的process.versions
是:
{
node: '13.2.0',
v8: '7.9.317.23-node.20',
uv: '1.33.1',
zlib: '1.2.11',
brotli: '1.0.7',
ares: '1.15.0',
modules: '79',
nghttp2: '1.40.0',
napi: '5',
llhttp: '1.1.4',
openssl: '1.1.1d',
cldr: '35.1',
icu: '64.2',
tz: '2019c',
unicode: '12.1'
}
複製代碼
由此驗證了N-API在兼容性和編譯這塊作的確實夠好。
以demo中的爲例子,分別用NAN和N-API實現了一個md5,下圖是兩者的對比:
咱們逐條逐條分析。
Tips:NAN使用第三方包nan,N-API使用第三方包node-addon-api
從頭部能夠看出N-API的頭文件更加乾淨清爽,沒有那些v8
的語句。由於不須要再像NAN那樣使用:
using v8::Local;
using v8::Object;
using v8::String
複製代碼
實現部分,NAN使用宏定義將實現的函數頭部包裹起來,而N-API通過node-addon-api
包裹以後,更像一個正常的函數,有函數名、形參、返回值。函數體實現兩者差距不是很大,除了返回值:
NAN:
info.GetReturnValue().Set(New(md5str).ToLocalChecked());
很是的v8!
而N-API:
return String::New(env, md5str,32);
很是的正常!
兩者的區別一看就看出差距:
NAN:
Nan::HandleScope scope;
Nan::SetMethod(exports, "md5", md5);
複製代碼
N-API:
exports.Set("md5", Function::New(env, GetMD5));
return exports;
複製代碼
NAN模塊的初始化是交給 Node.js 提供的宏來實現的:
NODE_MODULE(addon, init)
而N-API使用本身的宏定義(NAPI_MODULE
),由於咱們使用node-addon-api
,因此它也對這個宏定義包裹成下面這個了:
NODE_API_MODULE(addon, Init)
咱們來看看使用N-API對排序算法的一個效率提高,示例中使用了兩種排序算法:冒泡排序和快速排序:
代碼參考:sort.cc
好比以快速排序爲例子,快速排序的算法時間複雜度是NlogN,C語言版本的快排:
void quickSort(unsigned int *array, unsigned int length)
{
unsigned int partition;
unsigned int i, j;
unsigned int rightLength, leftLength;
unsigned int *rightArray, *leftArray;
if (length < 2)
{
return;
}
partition = *(array);
i = 1;
for (j = 1; j <= length; j++)
{
if (*(array + j) < partition)
{
swap(array + i, array + j);
i++;
}
}
swap(array, array + i - 1);
leftLength = i - 1;
leftArray = array;
rightArray = array + i;
rightLength = length - i;
quickSort(rightArray, rightLength);
quickSort(leftArray, leftLength);
}
複製代碼
當咱們改變demo中test/index.js
中數組的長度,獲得的js版本排序時間和N-API的排序時間(單位ms)以下圖:
從圖中能夠看到,在快速排序算法中,隨着數組長度的增長,js版本的排序時間甚至還優於N-API的排序時間,能夠說兩者不相上下,而在冒泡排序中,N-API的排序時間一直是優於js版本的排序時間,得出兩個結論: