導(dǎo)語(yǔ):當(dāng)Javascript的性能遭遇瓶頸,或者需要增強(qiáng)Javascript能力的時(shí)候,就需要依賴native模塊來(lái)實(shí)現(xiàn)了。
應(yīng)用場(chǎng)景
日常工作中,我們經(jīng)常需要將原生的Node.js模塊做為依賴并在項(xiàng)目中進(jìn)行使用。下面有個(gè)列表,你可能對(duì)它們的名字很熟悉:
node-sass 將sass文件編譯成css文件
node-microtime: 擴(kuò)展Javascript的時(shí)間精度
node-inspector:進(jìn)行調(diào)試
v8-profiler:性能及內(nèi)存使用分析
通常,我們開(kāi)發(fā)原生Node.js模塊包括但不僅限于以下原因:
對(duì)性能有比較苛刻要求的應(yīng)用。盡管Node.js得益于libuv,在異步I/O操作很有優(yōu)勢(shì),但遇到數(shù)字計(jì)算時(shí)并不是一個(gè)很好的選擇。
使用更加底層的API,比如操作系統(tǒng)層面的。
在C/C++和Node.js之間創(chuàng)建一個(gè)Bridge,進(jìn)行通信。
什么是原生模塊?
Node.js Addons是動(dòng)態(tài)鏈接的可共享對(duì)象,由C/C++編寫(xiě)而成??梢栽贜ode.js中通過(guò)require()
方法進(jìn)行調(diào)用,使用起來(lái)像調(diào)用Node.js普通模塊一樣。 —— 來(lái)自Node.js官方文檔
這意味著如果處理得當(dāng)?shù)脑挘K調(diào)用者使用由C/C++編寫(xiě)的原生模塊的方式和由Node.js編寫(xiě)的模塊一樣。想要編寫(xiě)Node.js addons,你需要了解一些基本知識(shí):
推薦閱讀這些資料。
創(chuàng)建Node.js的原生擴(kuò)展模塊
下面我以一個(gè)常見(jiàn)的動(dòng)態(tài)規(guī)劃問(wèn)題-青蛙跳臺(tái)階為例子來(lái)說(shuō)明如何創(chuàng)建一個(gè)原生的Node.js模塊。青蛙跳臺(tái)階描述為:一只青蛙一次可以跳上一級(jí)臺(tái)階,也可以跳上2級(jí)臺(tái)階,求該青蛙跳上n級(jí)臺(tái)階的共有多少種跳法?
首先創(chuàng)建一個(gè)frog_jump.cc原生文件,.cc的意思是c with class,擴(kuò)展名也可以是.cpp。Google Style Guide建議使用.cc,那么此處還是以.cc做為擴(kuò)展名吧。代碼如下:
#include <node.h>#include<vector>/** * Native method, calculate all ways frog jump to a target stair. */int climbStairs(int n) { std::vector<int> dp(n); dp[1] = 1; dp[2] = 2; for (int i = 3; i <= n; i ++ ) { dp[i] = dp[i - 1] + dp[i - 2]; } return dp[n]; }/** * Export native method jumpTo */void JumpTo(const v8::FunctionCallbackInfo<v8::Value>& args) { v8::Isolate* isolate = args.GetIsolate(); // Check input type if (!args[0] -> IsNumber()) { isolate -> ThrowException(v8::Exception::TypeError( v8::String::NewFromUtf8(isolate, "Wrong arguments type!"))); } int value = climbStairs(args[0] -> NumberValue()); v8::Local<v8::Number> num = v8::Number::New(isolate, value); args.GetReturnValue().Set(num); }// init is entry point.void init(v8::Local<v8::Object> exports) { NODE_SET_METHOD(exports, "jumpTo", JumpTo); } NODE_MODULE(frog_jump, init)
對(duì)這段代碼的解釋:
#include "node.h" 是c++里面引入頭文件的方式,具體源碼:node.h,C++鏈接時(shí)會(huì)加載這個(gè)頭文件。頭文件里面引入了v8命名空間,我們可以通過(guò)
v8::
標(biāo)志來(lái)訪問(wèn)v8的接口。訪問(wèn)所有v8的類型,都需要使用v8::標(biāo)志通過(guò)args對(duì)象來(lái)訪問(wèn)Node.js傳遞過(guò)來(lái)的參數(shù),通過(guò)args也可以獲取調(diào)用相關(guān)信息。
通過(guò)v8::Isolate*可以獲取函數(shù)作用域,可以像JS里面一樣進(jìn)行變量賦值,而不用擔(dān)心垃圾回收問(wèn)題,垃圾回收器會(huì)自動(dòng)進(jìn)行。
args.GetReturnValue()可以對(duì)函數(shù)返回的結(jié)果進(jìn)行設(shè)置。
任何原生Node.js模塊都需要調(diào)用NODE_MODULE,NODE_MODULE是一個(gè)宏,它會(huì)進(jìn)行模塊注冊(cè)操作。
C++ 有豐富的內(nèi)置類型來(lái)保存數(shù)字或者字符串,但是JS只能識(shí)別v8::里面定義的類型。因此,將c++的變量賦值給JS時(shí),需要轉(zhuǎn)換成可以被JS識(shí)別的類型,也即是v8::定義的類型。比如v8::String、v8::Object。
編譯原生的Node.js模塊
一旦源代碼編寫(xiě)完成,需要將它編譯成二進(jìn)制的addon.node
文件,之后才能被Node.js require。為了完成編譯操作,需要在項(xiàng)目的根目錄創(chuàng)建binding.gyp文件,里面定義了Build的配置。binding.gyp的內(nèi)容是一個(gè)JSON。
{ "targets": [ { "target_name": "frog_jump", "sources": [ "frog_jump.cc" ] } ] }
編譯環(huán)境配置:
windows: 以管理員的身份運(yùn)行npm install --global --production windows-build-tools,這個(gè)會(huì)安裝所有編譯依賴的工具。
osx: 安裝xcode
雖然npm內(nèi)置了一個(gè)node-gyp版本,但是這個(gè)版本沒(méi)有開(kāi)放給開(kāi)發(fā)者進(jìn)行調(diào)用。npm install的時(shí)候會(huì)調(diào)用它來(lái)進(jìn)行編譯和安裝工作。因此,開(kāi)發(fā)者想要調(diào)用node-gyp必須自己安裝一個(gè)全局的node-gyp版本。
$ npm install node-gyp -g $ node-gyp configure $ node-gyp build
運(yùn)行node-gyp configure命令會(huì)生成一個(gè)跨平臺(tái)的build文件,unix環(huán)境會(huì)生成Makefile,windows環(huán)境會(huì)在build目錄里面生成vcxproj。
運(yùn)行node-gyp build命令會(huì)生成可被Node.js調(diào)動(dòng)的addon.node二進(jìn)制文件。
Node.js中調(diào)用原生模塊
const frogJump = require('./build/Release/frog_jump');frogJump.jumpTo(20); //青蛙跳到第20個(gè)臺(tái)階的所有方法
項(xiàng)目源代碼:frog-jump
后續(xù)
nan,即Native Abstractions for Node.js。它基于Node.js API接口,兼容所有Node版本,目前的最佳實(shí)踐是基于nan來(lái)擴(kuò)展原生模塊,而不是直接使用Node.js API。
N-API,Node官方推出的用來(lái)編寫(xiě)原生Node擴(kuò)展模塊,是V8和nan的替代,目前處于實(shí)驗(yàn)階段。
http://www.cnblogs.com/cpselvis/p/6926157.html