导语:当Javascript的性能遭遇瓶颈,或者需要增强Javascript能力的时候,就需要依赖native模块来实现了。

应用场景

日常工作中,我们经常需要将原生的Node.js模块做为依赖并在项目中进行使用。下面有个列表,你可能对它们的名字很熟悉:

通常,我们开发原生Node.js模块包括但不仅限于以下原因:

  • 对性能有比较苛刻要求的应用。尽管Node.js得益于libuv,在异步I/O操作很有优势,但遇到数字计算时并不是一个很好的选择。
  • 使用更加底层的API,比如操作系统层面的。
  • 在C/C++和Node.js之间创建一个Bridge,进行通信。

什么是原生模块?

Node.js Addons是动态链接的可共享对象,由C/C++编写而成。可以在Node.js中通过require()方法进行调用,使用起来像调用Node.js普通模块一样。 —— 来自Node.js官方文档

这意味着如果处理得当的话,模块调用者使用由C/C++编写的原生模块的方式和由Node.js编写的模块一样。想要编写Node.js addons,你需要了解一些基本知识:

推荐阅读这些资料。

创建Node.js的原生扩展模块

下面我以一个常见的动态规划问题-青蛙跳台阶为例子来说明如何创建一个原生的Node.js模块。青蛙跳台阶描述为:一只青蛙一次可以跳上一级台阶,也可以跳上2级台阶,求该青蛙跳上n级台阶的共有多少种跳法?

首先创建一个frog_jump.cc原生文件,.cc的意思是c with class,扩展名也可以是.cpp。Google Style Guide建议使用.cc,那么此处还是以.cc做为扩展名吧。代码如下:

#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)

对这段代码的解释:

  • #include "node.h" 是c++里面引入头文件的方式,具体源码:node.h,C++链接时会加载这个头文件。头文件里面引入了v8命名空间,我们可以通过v8::标志来访问v8的接口。访问所有v8的类型,都需要使用v8::标志
  • 通过args对象来访问Node.js传递过来的参数,通过args也可以获取调用相关信息。
  • 通过v8::Isolate*可以获取函数作用域,可以像JS里面一样进行变量赋值,而不用担心垃圾回收问题,垃圾回收器会自动进行。
  • args.GetReturnValue()可以对函数返回的结果进行设置。
  • 任何原生Node.js模块都需要调用NODE_MODULE,NODE_MODULE是一个宏,它会进行模块注册操作。
  • C++ 有丰富的内置类型来保存数字或者字符串,但是JS只能识别v8::里面定义的类型。因此,将c++的变量赋值给JS时,需要转换成可以被JS识别的类型,也即是v8::定义的类型。比如v8::String、v8::Object。

编译原生的Node.js模块

一旦源代码编写完成,需要将它编译成二进制的addon.node文件,之后才能被Node.js require。为了完成编译操作,需要在项目的根目录创建binding.gyp文件,里面定义了Build的配置。binding.gyp的内容是一个JSON。

{
"targets": [
{
"target_name": "frog_jump",
"sources": [ "frog_jump.cc" ]
}
]
}

编译环境配置:

  • windows: 以管理员的身份运行npm install --global --production windows-build-tools,这个会安装所有编译依赖的工具。
  • linux: 安装python v2.7makeGCC
  • osx: 安装xcode

虽然npm内置了一个node-gyp版本,但是这个版本没有开放给开发者进行调用。npm install的时候会调用它来进行编译和安装工作。因此,开发者想要调用node-gyp必须自己安装一个全局的node-gyp版本。

$ npm install node-gyp -g
$ node-gyp configure
$ node-gyp build

运行node-gyp configure命令会生成一个跨平台的build文件,unix环境会生成Makefile,windows环境会在build目录里面生成vcxproj。

运行node-gyp build命令会生成可被Node.js调动的addon.node二进制文件。

Node.js中调用原生模块

const frogJump = require('./build/Release/frog_jump');

frogJump.jumpTo(20);  //青蛙跳到第20个台阶的所有方法

项目源代码:frog-jump

后续

nan,即Native Abstractions for Node.js。它基于Node.js API接口,兼容所有Node版本,目前的最佳实践是基于nan来扩展原生模块,而不是直接使用Node.js API。

N-API,Node官方推出的用来编写原生Node扩展模块,是V8和nan的替代,目前处于实验阶段。

编写原生的Node.js模块的更多相关文章

  1. 编写原生Node.js模块

    导语:当Javascript的性能需要优化,或者需要增强Javascript能力的时候,就需要依赖native模块来实现了. 应用场景 日常工作中,我们经常需要将原生的Node.js模块做为依赖并在项 ...

  2. 10、Node.js模块系统

    ##################################################################################介绍Node.js模块系统为了让No ...

  3. Node.js模块

    每一个Node.js都是一个Node.js模块,包括JavaScript文件(.js).JSON文本文件(.json)和二进制模块文件(.node). mymodul.js function Hell ...

  4. 如何发布一个自定义Node.js模块到NPM(详细步骤)

    咱们闲话不多说,直接开始! 由于我从没有使用过MAC,所以我不保证本文中介绍的操作与MAC一致. 文章开始我先假定各位已经在window全局安装了Node.js,下面开始进行详细步骤介绍: 本文本着, ...

  5. Node.js模块封装及使用

    Node.js中也有一些功能的封装,类似C#的类库,封装成模块这样方便使用,安装之后用require()就能引入调用. 一.Node.js模块封装 1.创建一个名为censorify的文件夹 2.在c ...

  6. 如何发布一个自定义Node.js模块到NPM(详细步骤,附Git使用方法)

    咱们闲话不多说,直接开始! 由于我从没有使用过MAC,所以我不保证本文中介绍的操作与MAC一致. 文章开始我先假定各位已经在window全局安装了Node.js,下面开始进行详细步骤介绍: 本文本着, ...

  7. node.js 模块和其下载资源的镜像设置

    以前安装 electron 时总是失败,然后就在淘宝镜像上下载好相应版本的文件放到用户目录来解决问题. 后来研究发现 npm 不仅可以设置 node.js 模块仓库的代理, 同样可以设置像 elect ...

  8. Developer - 如何自我保证Node.js模块质量

    组里正在做SaaS产品,其中一些模块(Module)是Node.js实现,这里我们主要使用Node.js实现Web Server来提供服务. 在做SaaS项目之前,组里的开发模式是传统的Deverlo ...

  9. Node Js模块讲解

    Node JS模块 所谓的Node JS模块其实就是指Node JS package,即nodejs包. 一 什么是NodeJS模块? 在说这个问题之前,我们有必要提出一个概念,即模块规范. 现阶段J ...

随机推荐

  1. node.js系列(实例):原生node.js实现静态资源管理

    /** * node入门之综合案例(一):简易路由 * @Author : by Ghost * @Date : 2016/07/11 * @Description : * 1.引入以下模块 * ht ...

  2. js解析器(重要!)

    JavaScript有"预解析"的特性,理解预解析是很重要的,不然在实际开发中可能会遇到很多无法解析的问题,甚至导致程序bug的存在. #js预解析执行过程: 预解析:(全局作用域 ...

  3. Vim常用操作-快速删除括号中内容。

    如果你和我一样,希望拥有众多工具,发挥工具最大执行效率,让工作事半功倍的话,我推荐你来使用下 Vim. 刚接触Vim 会觉得它的学习曲线非常陡峭,要记住很多命令,操作太复杂.所以这个系列的分享,不会教 ...

  4. python基本数据类型——dict

    一.创建字典: d = { "name": "morra", #字典是无序的 "age": 99, "gender": ...

  5. Dora.Interception: 一个为.NET Core度身定制的AOP框架

    多年从事框架设计开发使我有了一种强迫症,那就是见不得一个应用里频繁地出现重复的代码.之前经常Review别人的代码,一看到这样的程序,我就会想如何将这些重复的代码写在一个地方,然后采用“注入”的方式将 ...

  6. 蓝桥杯-逆波兰表达式-java

    /* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...

  7. NGINX中的proxy_pass和rewrite

    文章作者:luxianghao 文章来源:http://www.cnblogs.com/luxianghao/p/6807081.html 转载请注明,谢谢合作. 免责声明:文章内容仅代表个人观点,如 ...

  8. 配置web.xml和glassfish容器实现javaEE表单验证

    web.xml配置: <!-- 声明用于安全约束的角色 --> <security-role> <role-name>ReimUser</role-name& ...

  9. C#基础之------委托

    一.委托的基本介绍 可以任务委托是持有一个或多个方法的对象.当然,正常情况下你不会去执行一个对象,但是委托与对象不同.可以执行委托,这是委托就会执行他所"持有"的方法. 举个栗子就 ...

  10. Mysql数据库学习笔记之数据库索引(index)

    什么是索引: SQL索引有两种,聚集索引和非聚集索引,索引主要目的是提高了SQL Server系统的性能,加快数据的查询速度与减少系统的响应时间. 聚集索引:该索引中键值的逻辑顺序决定了表中相应行的物 ...