JavaScript的变迁
2.1 CommonJS规范
希望JavaScript能够在任何地方运行
2.1.1 CommonJS的出发点
针对JavaScript自身的缺陷:
①没有模块系统
②标准库较少
③没有标准接口
④缺乏包管理系统
希望不仅可以利用JavaScript开发富客户端应用还可以编写:
①服务器端JavaScript应用程序
②命令行工具
③桌面图形界面应用程序
④混合应用(Titanium和Adobe AIR等形式的应用)
CommonJS规范涵盖了模块、二进制、Buffer、字符集编码、I/O流、进程环境、文件系统、套接字、单元测试、Web服务器网关接口、包管理
2.1.2 CommonJS的模块规范
CommonJS对模块的定义分为模块引用、模块定义和模块标识
模块引用的示例代码如下:
var math=require('math');
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);
};
模块标识即传递给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对象。
exports与module.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 [email protected]
gyp infousing [email protected] | 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,
CommonJS为package.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) {
// 检测上