# 模块机制
# CommonJS
exports
和module.exports
# 模块实现
# 模块引入
模块分为两类:Node
提供的核心模块和用户编写的文件模块。
在Node
中引入模块,需要经历三个步骤:
路径分析
文件定位
编译执行
在Node
启动时,部分核心模块就被直接加载进内存,所以这部分核心模块引入时,无须文件定位和编译执行步骤,加载速度最快。
文件模块在运行时动态加载,速度较慢。
# 缓存加载
Node
会对引入过的模块进行缓存,以减少二次引入时的开销。
浏览器仅缓存文件,Node
缓存的是编译和执行之后的对象。
编译成功后会将文件路径作为索引缓存在Module._cache
(核心模块为NativeModule._cache
)对象上。
# 路径分析和文件定位
模块标识符在Node
中主要分为:
核心模块,如
http
、fs
等。相对路径文件模块。
绝对路径文件。
非路径形式的文件模块,如自定义的
connect
模块。
查找耗时:缓存 < 核心 < 路径 < 自定义
自定义模块查找策略是:从当前目录沿路径向上逐级递归,直到根目录的 node_modules
目录。
允许标识符中不含文件扩展名,Node
会按.js
、.json
、.node
的次序依次尝试。在尝试的过程中,需要调用fs
模块同步阻塞式地判断文件是否存在,所以最好补齐扩展名。可能最后查找到的是一个目录,Node
会在当前目录下查找package.json
,通过JSON.parse
解析出包描述对象,取出main
属性指定的文件名进行定位,如果没有package
文件,会将index
作为默认文件名依次查找。
# 编译
对于不同的扩展名,载入方法不同:
.js
文件。通过fs
模块同步读取文件后编译执行。.node
文件。这是用C/C++
编写的,通过dlopen
方法加载。.json
文件。通过fs
同步读取后用JSON.parse
解析返回结果。其余扩展文件都被当做
.js
文件载入。
require.extensions
定义了上面三种扩展名的加载方式。可以通过类似require.extensions[.ext]
的方式实现对自定义扩展名的特殊加载。
JavaScript
模块的编译:在编译的过程中,Node
对于获取的文件都在头部添加了(function(exports, require, module, __filename, __dirname) {})
,这样实现了作用域隔离。包装后的代码会通过vm
原生模块的runInThisContext
(类似eval
,不污染全局)方法执行,返回一个function
对象。引入时将exports
返回给调用方。
C/C++
模块的编译:无需编译,只用加载和执行。执行效率较高,门槛也相对较高。
JSON
文件的编译:通过fs
同步读取后用JSON.parse
解析。
# AMD
define(id?, dependicies?, factory)
声明模块时指定所有的依赖,通过形参传递依赖到模块中。
define(['dep1', 'dep2'], function (dep1, dep2) {
return function (){}
})
2
3
# CMD
支持动态引入
define(function (require, exports, module) {
})
2
3
# 兼容多种模块规范
(function (name, definition) {
var hasDefine = typeof define === 'function',
hasExports = typeof module !== "undefined" && module.exports;
if (hasDefine) {
define(definition);
} else if (hasExports) {
module.exports = definition();
} else {
// 浏览器
this[name] = definition();
}
})('hello', function () {
var hello = function () {};
return hello;
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16