《深入浅出Node.js》学习笔记——(二)模块机制


                                 JavaScript的变迁

 

2.1 CommonJS规范

希望JavaScript能够在任何地方运行

2.1.1 CommonJS的出发点

针对JavaScript自身的缺陷:

①没有模块系统

②标准库较少

③没有标准接口

④缺乏包管理系统

 

希望不仅可以利用JavaScript开发富客户端应用还可以编写:

①服务器端JavaScript应用程序

②命令行工具

③桌面图形界面应用程序

④混合应用(TitaniumAdobe AIR等形式的应用)

 

CommonJS规范涵盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、Web服务器网关接口、包管理

 


 

2.1.2 CommonJS的模块规范

CommonJS对模块的定义分为模块引用、模块定义和模块标识

  1. 模块引用

模块引用的示例代码如下:

var math=require('math');

  1. 模块定义

exports用于导出,是module的属性。将方法挂载在exports对象上作为属性即可定义导出的方式:

//math.js

exports.add=function(){

   var sum=0,

   i=0,

   args=arguments,

   l=args.length;

   while(i<1){

sum+=args[i++];

}

Return sum;

};

在另一个文件中调用:

//program.js

Var math=require('math');

Exports.increment=function(val){

Return math.add(val,1);

};

  1. 模块标识

模块标识即传递给require()方法的参数

模块的意义在于将类聚的方法和变量等限定在私有的作用域中,同时支持引入和导出功能以顺畅地连接上下游依赖。

 

2.2 Node的模块实现

Node中引入模块需要经历的步骤:

①路径分析

②文件定位

③编译执行

 

模块分为两类:①核心模块 ②文件模块

 

2.2.1 优先从缓存加载

Node对引入过的模块会进行缓存,缓存的是编译和执行后的对象。核心模块的缓存检查先于文件模块的缓存检查。

 

2.2.2 路径分析和文件定位

1.模块标识符分析

模块标识符在Node中主要分为以下几类:

①核心模块

②相对路径文件模块

③绝对路径文件模块

④非路径形式的文件模块,如自定义的connect模块

2.文件定位

①文件扩展名分析

Node按照.js.json.node的次序补足扩展名

②目录分析和包

 

2.2.3模块编译

.js文件。通过fs模块同步读取文件后编译执行。

.node文件。用C/C++编写的扩展文件,通过dlopen()方法加载最后编译生成的文件。

.json文件。通过fs模块同步读取文件后,用JSON.parse()解析返回结果。

④其余扩展名文件。它们都被当做.js文件载入。

1.JavaScript模块的编译

           每个模块文件之间进行作用域隔离,执行后返回一个具体function对象。

           exportsmodule.exports

           2.C/C++模块编译

           Node调用process.dlopen()方法进行加载和执行,使用libuv兼容层封装。

           .node模块文件不需要编译。

           3.JSON文件的编译

           Node利用fs模块同步读取JSON文件的内容之后,调用JSON.parse()方法得到对象。

           如果作为配置,就不必调用fs模块去异步读取和解析,直接require()引入即可。

 

2.3 核心模块

核心模块分为C/C++JavaScript编写的两部分,其中C/C++文件放在src目录下,JavaScript文件存放在lib目录下。

2.3.1 JavaScript核心模块的编译过程

1、转存为C/C++代码

使用V8附带的js2c.py工具,将内置的JS代码转换成C++里的数组,生成node_natives.h头文件。

namespacenode {

constchar node_native[] = { 47, 47, ..};

constchar dgram_native[] = { 47, 47, ..};

constchar console_native[] = { 47, 47, ..};

constchar buffer_native[] = { 47, 47, ..};

constchar querystring_native[] = { 47, 47, ..};

constchar punycode_native[] = { 47, 42, ..};

...

struct_native {

constchar* name;

constchar* source;

size_tsource_len;

};

staticconst struct _native natives[] = {

{"node", node_native, sizeof(node_native)-1 },

{"dgram", dgram_native, sizeof(dgram_native)-1 },

...

};

}

JS代码以字符串形式存在node命名空间中,不可直接执行。

启动Node进程时,JS代码直接加载进内存中。

2.编译JavaScript核心模块

同样经历头尾包装,执行和导出exports对象。

与文件模块有区别的地方在于:

①获取源代码的方式

②缓存执行结果的位置

 

2.3.2 C/C++核心模块的编译过程

1.内建模块的组织形式

structnode_module_struct {

intversion;

void*dso_handle;

constchar *filename;

void(*register_func) (v8::Handle<v8::Object> target);

constchar *modname;

};

内建模块定义后,通过NODE_MODULE宏将模块定义到node命名空间中,模块的具体初始化方法挂载为结构的register_func成员:

#defineNODE_MODULE(modname, regfunc)                                                             \

extern"C" {                                                                                                                          \

NODE_MODULE_EXPORTnode::node_module_struct modname ## _module =  \

{                                                                                                                                            \

NODE_STANDARD_MODULE_STUFF,                                                                            \

regfunc,                                                                                                                               \

NODE_STRINGIFY(modname)                                                                                         \

};                                                                                                                                           \

}

Node_extensions.h文件将内建模块放进了node_module_list数组中,

包括:

Node_buffer,node_crypto, node_evals, node_fs, node_http_parser, node_os, node_zlib,node_timer_wrap, node_tcp_wrap, node_udp_wrap, node_pipe_wrap, node_cares_wrap,node_tty_wrap, node_process_wrap, node_fs_event_wrap, node_signal_watcher

使用get_builtin_module()取出模块

内建模块的优势:

①性能优于脚本语言

②直接加载进内存执行

2.内建模块的导出


node在启动时,生成全局变量process,并提供binding()方法协助加载内建模块

Binding()的实现代码在src/node.cc

staticHandle<Value> Binding(const Arguments& args) {

HandleScopescope;

Local<String>module = args[0]->ToString();

String::Utf8Valuemodule_v(module);

node_module_struct*modp;

if(binding_cache.IsEmpty()) {

binding_cache= Persistent<Object>::New(Object::New());

}

Local<Object>exports;

if(binding_cache->Has(module)) {

exports= binding_cache->Get(module)->ToObject();

returnscope.Close(exports);

}

//Append a string to process.moduleLoadList

charbuf[1024];

snprintf(buf,1024, "Binding %s", *module_v);

uint32_tl = module_load_list->Length();

module_load_list->Set(l,String::New(buf));

if((modp = get_builtin_module(*module_v)) != NULL) {

exports= Object::New();

modp->register_func(exports);

binding_cache->Set(module,exports);

} elseif (!strcmp(*module_v, "constants")) {

exports= Object::New();

DefineConstants(exports);

binding_cache->Set(module,exports);

#ifdef__POSIX__

} elseif (!strcmp(*module_v, "io_watcher")) {

exports= Object::New();

IOWatcher::Initialize(exports);

binding_cache->Set(module,exports);

#endif

} elseif (!strcmp(*module_v, "natives")) {

exports= Object::New();

DefineJavaScript(exports);

binding_cache->Set(module,exports);

} else {

returnThrowException(Exception::Error(String::New("No such module")));

}

returnscope.Close(exports);

}

2.3.3核心模块的引入流程


 

2.3.4 编写核心模块

编写头文件,编写C++文件,更改src/node_extensions.h,更改node的项目生成文件,编译,安装

 

2.4 C/C++扩展模块

属于文件模块中的一类


 

2.4.1前提条件

GYP项目生成工具

V8引擎C++

libuv

Node内部库

⑤其他库

2.4.2 C/C++扩展模块的编写

无须将源代码写进node命名空间,也不需要提供头文件

2.4.3 C/C++扩展模块的编译

编写.gyp项目文件

{

'targets':[

{

'target_name':'hello',

'sources':[

'src/hello.cc'

],

'conditions':[

['OS =="win"',

{

'libraries':['-lnode.lib']

}

]

]

}

]

}

然后调用

$node-gyp configure

会得到如下的输出结果:

gyp infoit worked if it ends with ok

gyp infousing node-gyp@0.8.3

gyp infousing node@0.8.14 | darwin | x64

gyp infospawn python

gyp infospawn args [ '/usr/local/lib/node_modules/node-gyp/gyp/gyp',

gyp infospawn args 'binding.gyp',

gyp infospawn args '-f',

gyp infospawn args 'make',

gyp infospawn args '-I',

gyp infospawn args'/Users/jacksontian/git/diveintonode/examples/02/addon/build/config.gypi',

gyp infospawn args '-I',

gyp infospawn args '/usr/local/lib/node_modules/node-gyp/addon.gypi',

gyp infospawn args '-I',

gyp infospawn args '/Users/jacksontian/.node-gyp/0.8.14/common.gypi',

gyp infospawn args '-Dlibrary=shared_library',

gyp infospawn args '-Dvisibility=default',

gyp infospawn args '-Dnode_root_dir=/Users/jacksontian/.node-gyp/0.8.14',

gyp infospawn args'-Dmodule_root_dir=/Users/jacksontian/git/diveintonode/examples/02/addon',

gyp infospawn args '--depth=.',

gyp infospawn args '--generator-output',

gyp infospawn args 'build',

gyp infospawn args '-Goutput_dir=.' ]

gyp infook

 

2.4.4 C/C++扩展模块的加载

Require()在引入.node文件的过程中,经历了四个层面的调用

加载.node文件的两个步骤:

①调用uv_dlopen()方法打开动态链接库

②调用uv_dlsym()找到动态链接库中通过NODE_MODULE宏定义的方法地址

两个过程都通过libuv库进行封装


 

2.5 模块调用栈


 

2.6 包与NPM


 

2.6.1 包结构

包目录包含的文件:package.json,bin, lib, doc, test

2.6.2 包描述文件与NPM

包描述文件是一个JSON格式的文件——package.json

CommonJSpackage.json文件定义了一些必需字段:

Name,description, version, keywords, maintainers, contributors, bugs, licenses,repositories, dependencies, homepage, os, cpu, engine, builtin, directories,implements, scripts

2.6.3 NPM常用功能

1.查看帮助 2.安装依赖包 3.NPM钩子命令 4.发布包 5.分析包

2.6.4 局域NPM


2.6.5 NPM潜在问题

包质量和安全问题

口碑效应

 

2.7前后端共用模块

2.7.1 模块的侧重点

Node模块引入同步,前端模块异步。

2.7.2 AMD规范

define明确定义一个模块,而在Node中是隐式包装的

用返回的方式实现导出

2.7.3 CMD规范

AMD区别于定义模块和依赖引入部分

2.7.4 兼容多种模块规范

;(function(name, definition) {

// 检测上

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值