node的模块实现
node中模块分为两类:
- 核心模块:Node提供的模块
- 文件模块:用户编写的模块
require接收一个标识符作为参数,Node正是基于这个标识符进行模块查找的。模块标识符有以下三种形式:
- 核心模块名称,如http、fs、path等,试图加载一个与核心模块标识符相同的自定义模块是不会成功的,除非换用路径的方式
- 路径形式的文件模块,以 . 、 .. 或 / 开头的标识符,都被当做文件模块来处理
- 自定义模块,自定义模块指的是非核心模块,也不是路径形式的标识符(就是node_modules中的包)。它是一种特殊的文件模块,可能是一个文件或者包的形式。这类模块的查找是最费时的,也是所有方式中最慢的
Node是如何查找自定义模块的(模块路径生成规则)
- 当前目录下的node_modules目录
- 父目录下的node_modules目录
- 父目录的父目录下的node_modules目录
- 沿路径向上逐级递归,直到根目录下的node_modules目录(直到找到文件为止)
- 如果遍历完毕依然没有查找到目标,则会抛出查找失败的异常
在node中引入模块,需要经历以下三个步骤:
- 路径分析: 只有自定义模块需要按上述规则分析路径
- 文件定位
- 文件扩展名分析: 如果标识符不包含文件扩展名,Node会按 .js .json .node 的次序依次尝试
- 目录分析和包: 如果根据标识符找到的是一个目录,Node会把这个目录当做一个包来处理:
- 在当前目录下查找package.json,解析出main属性指定的文件名
- 如果main属性指定的文件名无效或压根没有package.json,Node会将index当做默认文件名,依次查找 index.js、 index.json 、index.node
- 编译执行: Node新建一个模块对象,根据路径载入并编译。对于不同文件扩展名,载入方法也有所不同。在确定了文件的扩展名之后,Node将调用具体的编译方式来讲文件执行后返回给调用者。
- .js文件:通过fs模块同步读取文件后编译文件
- .node文件:这是用C/C++编写的扩展文件,通过dlopen()方法加载最后编译
- .json文件: 通过fs模块同步读取文件后,用JSON.parse()解析返回结果
其他扩展名:都被当做.js文件载入
JavaScript模块的编译
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.cjildren = [] }
编译过程中,Node对获取的JavaScript文件内容进行的头尾包装,在头尾分别添加了:
(function (exports, require, module, __filename, __dirname){\n
文件内容
\n})
使用vm原生模块的runInThisContext()方法执行(类似eval)包装后的文件代码,返回一个具体的function对象
将当前模块对象的exports属性、require方法、module(模块对象自身)以及在文件定位中得到的完整文件路径和文件目录作为参数传给这个function执行
AMD
define([id], [dependencies], factory);
definde(function() {
var exports = {};
实际模块代码
return exports;
})
CMD
definde(['dep1', 'dep2'], function(dep1, dep2){
return function(){}
}
UMD
(function (name, definition){
//检测上下文环境是否为AMD或CMD
var hasDefine = typeof define === 'function',
//检测上下文环境是否为Node
hasExports = typeof module !== 'undefined' && module.exports;
if(hasDefine){
//AMD或CMD环境
define(definition)
}else if(hasExports){
//Node
module.exports = definition()
}else{
//window
this[name] = definition()
}
})('my-module', function(){
return function(){console.log('hello world')}
});