深入浅出Node.js读书笔记:Node.js的模块实现(2.2)

2.2 Node模块的实现

Node并不是完全按照规范实现,而是进行了一定的取舍,并同时增加了少许自身需要的特性。

Node引入模块时,需要经历如下几个步骤:
1.路径分析。
2.文件定位。
3.编译执行。

Node中提供的模块分为两类,一类是node自身提供称为核心模块;另一类是用户自定义称为文件模块。

Node的核心模块,在编译源码时,就已经编译进二进制可执行文件。在Node启动时,核心模块会加载到内存,这部分模块引入时,文件定位和编译执行这两步会省略,并在路径分析中优先判断,所以它的加载速度很快。

文件模块则在运行时动态加载,需要完整的经历以上三个步骤,速度相对比核心模块慢。

2.2.1 优先从缓存加载

与前端浏览器的实现一样,对已经引入过的模块,Node会暂存在内存中,以加速第二次引入模块的速度。但不同的是,浏览器只缓存文件,而Node缓存的是编译和执行后的对象。

2.2.2 路径分析和文件定位

标识符有几种形式,针对不同的标识符,查找和定位有一定的差异。

1.模块标识符分析

require接收一个标识符当作参数,模块标识符在Node中主要分为以下几类:

*.核心模块,比如http, fs, path等。
*…或者…开始的相对路径文件模块。
*.以/开始的绝对路径文件模块。
*.非路径形式的文件模块。

核心模块的优先级仅次于缓存,它在Node编译源码时就已经编译为二进制代码。如果加载一个与核心模块相同的标识符会失败,必须选择一个不同的标识符或者换用路径的方式。

以.或…开始的标识符,都会被当作文件模块来处理。require会把路径转换为真实路径,并以路径为索引,将编译执行后的结果存放到缓存中,以加快二次引用,由于指明了具体的路径,查找需要花费一定的时间,速度较核心模块慢。

自定义模块是非核心模块,也不是路径形式的标识符,查找速度最慢。

2.模块路径的生成规则(module.paths)
*.当前文件目录下的node_module目录。
*.父目录下的node_module目录。
*.父目录的父目录的node_module目录。
*.沿路径向上逐级递归,直到根目录下的node_module目录。

比如
'/home/jackson/research/node_modules', 
'/home/jackson/node_modules', 
'/home/node_modules', 
'/node_modules',

可以看出,当前文件路径越深,模块查找越耗时,这是自定义模块加载慢的根本原因。

3.文件定位

文件扩展名的分析

require在分析扩展名的时候,会出现标识符中不包含文件扩展名的情况。CommonJS规范也允许在标识符中不包含文件扩展名,在这种情况下,Node会按.js,.json,.node等次序补足扩展名。

目录分析和包
require在通过分析文件扩展名后,可能没有找到对应的文件,但却得到一个目录,此时Node会将目录当作一个包来处理,首先Node在当前目录下查找package.json,通过JSON.parse分析,从中取出main属性指定的文件名进行定位。

如果main指定的文件名错误,或者根本没有package.json,Node会将index当作默认文件名,然后依次查找index.js,index.json,index.node。

2.2.3 模块编译

在Node中,每个文件模块都是一个对象,定义如下:

	function module(id,parent){
		this.id = id;
		this.exports = {};
		this.parent = parent;
		if(parent && parent.children){
			parent.children.push(this);
		}
		this.filename = null;
		this.loaded = false;
		this.children = [];
	}

Node会新建一个模块对象,然后根据路径载入并编译,对于不同的扩展名,其载入方式也不太一样。

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

.node文件
这是通过c/c++编写的扩展文件,通过dlopen加载最后编译生成的文件。

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

每一种扩展文件,都对应一个解析该扩展文件的方法,比如输出如下代码:
console.log(require.extensions);

得到如下结果:
{’.js’:[function],’.json’:[function],’.node’:[function]}

1.javascript模块的编译(.js)

CommonJS规范定义,每个模块文件中存在require,exports,module这三个变量,但是它们在模块中并没有定义。

甚至在Node的API文档中,每个模块中还有__filename__,__dirname这两个变量,它们在哪里定义呢?

事实上,在编译的过程中,Node对获取的Javascript文件内容进行了头尾包装,如下:

	((function (exports,require,module,__filename__,__dirname){
		var math = require('math');
			exports.area = function(radius){
				return Math.PI * radius * radius;
			}
	})

如上我们看到,在Node编译整个js文件时,会把整个js文件进行包装,将如上几个参数当作方法参数传入,从而在整个js文件中使用时,能够被引用到。其中在exports中定义的属性和方法,都可以被外部调用到,但在模块中的其余变量或属性则不可直接被调用。

2.c/c++模块的编译(.node)

Node调用process.dlopen()方法进行加载和执行。dlopen在不同的平台实现不同,目前node是通过libuv兼容处理。

另外.node文件并不需要编译,它是在编写完c/c++模块之后编译生成。

c++模块给Node使用者带来的优势主要是执行效率方面,劣势则是c/c++模块的编写门槛比Javascript高。

3.json文件的编译(.json)

Node利用fs模块同步读取JSON文件的内容之后,调用JSON.parse()方法得到对象。然后将它赋给模块对象的exports,以供外部调用。JSON文件主要用在对项目的配置上,直接调用require就可以直接引用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值