深入浅出NodeJS笔记(二)

本文介绍了Node.js中的模块系统,包括CommonJS规范的应用、模块的引用与定义、核心与自定义模块的区别及加载机制,以及如何实现模块间的依赖管理。

第2章 模块机制

1. JavaScript 先天缺乏的功能:模块。(如Java中的类,Python的import机制等)

2. CommonJS规范的提出,为了弥补当前JavaScript没有标准的缺陷,使其具备开发大型应用的能力,包括服务端JavaScript应用程序,命令行工具,桌面图形界面应用程序,混合应用。

3. Node借鉴了CommonJS的Modules规范实现了一套模块系统,NPM对Packages规范的完好支持使得Node应用在开发过程中事半功倍。

4. 模块引用,模块定义和模块标识

通过require方法引用一个模块的API到当前上下文:

var http = require('http');

Exports对象用于导出当前模块的方法或变量,且它是唯一导出的出口。module对象代表模块本身,exports是其属性。在Node中,一个文件就是一个模块。导出的定义:

exports.get = function() {
  return "hello";
};

模块标识就是传递给require方法的参数,表示模块文件的路径。

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

6. 模块的引入包含三个阶段:路径分析,文件定位和编译执行。在Node进程启动时,部分核心模块就被直接加载到内存中,没有后两个阶段,加载速度较快。用户自定义的文件模块是运行时动态加载,加载速度慢于核心模块。Node对引入过的模块会进行缓存,并且缓存的是编译和执行后的对象。核心模块的缓存检查先于文件模块。

7.  如果试图加载一个与核心模块标识符相同的自定义模块是无法成功的。需选择一个不同的标识符或使用路径方式。

8. 自定义模块(第三方开发)的查找最慢。从当前目录开始,查找node_modules目录,如果没有查找其父目录,一直向上递归到根目录。例如,在module-config.js中写入代码:

console.log(module.paths);

执行该文件后,得到类似下面的输出,即为上述查找自定义模块的顺序:


9. 如果模块标识符不包含扩展名,Node会按.js,.json和.node的顺序尝试,这期间会调用fs模块同步阻塞式地判断文件是否存在。

10. 在定位自定义模块过程中,如果require分析扩展名后找到的是目录而不是文件,则按照包来处理。首先在包的当前目录下查找package.json文件,解析出main属性指定的文件名,默认为index.js。如果遍历了所有模块路径后都没有查找成功,则抛出异常。

11. 每个文件模块都封装为一个Module对象。根据扩展名不同,采用不同的加载方式。例如.js模块使用fs模块同步读取文件后编译执行。不同的加载方式保存在Module._extensions结构中,如Module._extensions['.json']。

12. 在编译时,Node对每个文件内容进行首尾包装,类似:

(function (exports, require, module, __filename, __dirname){
	var fs = require('fs');
	exports.get = function(){
		returen "hello";
	}
});

每个模块文件之间都进行了作用域隔离。该包装的函数执行后返回当前模块的exports变量给调用方。如果要达到require引入一个类的效果,赋值给module.exports对象。

13. Node核心模块部分由JavaScript编写,部分由C/C++编写。对于前者,首先将内置的JavaScript代码转换为C++数组(通过V8提供的工具),JavaScript代码以字符串的形式存储在node命名空间中。这样在加载时,JavaScript模块可直接定位到内存(NativeModule._source = process.binding('natives'))中,或者从缓存(NativeModule._cache)获取。对于后者,通常由C/C++完成核心部分,由JavaScript实现包装或向外导出,以满足性能需求。

14. 核心(内建)模块的组织。内建模块是完全由C\C++编写的的模块,通过NODE_MODULEHONG宏将模块定义到node命名空间中。通过模块结构成员register_func方法初始化。所有内建模块组织为一个node_module_list的数组,通过get_builtin_module方法获取模块。

15. 内建模块的导出。文件模块一般通过核心JavaScript模块来调用内建模块。在加载时,先创建一个exports空对象,然后调用get_builtin_module方法取出内建对象,通过执行register_func填充exports对象,最后将exports对象按模块名缓存并返回给调用方法。以上过程通过process.Binding方法完成。

16. 如果自定义核心模块,需要重新编译和安装Node项目。(书中附录有具体过程)

17. 梳理内建模块的调用过程(如os,fs等)。再次强调,文件模块依赖JavaScript核心模块,核心模块依赖内建模块。以require('os')这句代码为例,背后发生或已经发生了什么事情?

Node发现这是一个核心模块,调用NativeModule.require('os')。在该方法中,通过NativeModule._source['os']取出os模块的源码,调用runInThisContext方法执行。这里取到的模块是JavaScript核心模块os.js。模块的源码是哪里来的?在os.js头部有这样的代码:var binding = process.binding('os'); 该方法调用通过get_builtin_module方法取出模块对象,再通过register_func导出到exports对象并返回。再往前追述,就如第14条所示。process是node启动时的全局变量,在node.cc中,调用SetupProcessObject方法初始化process对象,并添加了binding等方法:NODE_SET_METHOD(process,"binding", Binding); 而Binding方法实际完成了加载内建模块的功能。从上面过程可以看出,JavaScript核心模块由两个功能:一是作为C/C++内建模块的封装层,二是存粹的模块功能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值