導(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ì)它們的名字很熟悉:

通常,我們開(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ì)安裝所有編譯依賴的工具。

  • linux: 安裝python v2.7、makeGCC

  • 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