BLOOK

GP's blog and book

module require源码解析

require 的用法

1
2
3
4
5
require():加载一个外部模块
· require.resolve():解析一个模块名到它的绝对路径
· require.main:主模块
· require.cache:所有缓存好的模块
· ·require.extensions:根据其扩展名,对于每个有效的文件类型可使用的编制方法

require 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//require
{ [Function: require]
resolve: [Function: resolve],
main:
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/gaopo/study/node/c.js',
loaded: false,
children: [],
paths:
[ '/Users/gaopo/study/node/node_modules',
'/Users/gaopo/study/node_modules',
'/Users/gaopo/node_modules',
'/Users/node_modules',
'/node_modules' ] },
extensions: { '.js': [Function], '.json': [Function], '.node': [Function] },
cache:
{ '/Users/gaopo/study/node/c.js':
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/gaopo/study/node/c.js',
loaded: false,
children: [],
paths: [Object] } } }

module 数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
Module {
id: '.',
exports: {},
parent: null,
filename: '/Users/gaopo/study/node/c.js',
loaded: false,
children: [],
paths:
[ '/Users/gaopo/study/node/node_modules',
'/Users/gaopo/study/node_modules',
'/Users/gaopo/node_modules',
'/Users/node_modules',
'/node_modules' ] }

require 的顺序

1
2
3
4
X/package.json(main字段)
X/index.js
X/index.json
X/index.node

源码解析

源码仓库:
https://github.com/nodejs/node-v0.x-archive/blob/master/lib/module.js

1、以模块的绝对路径(filename)作为模块的识别符。

2、然后,如果模块已经在缓存中,就从缓存取出;

1
2
3
4
5
var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}

3、如果不在缓存中,就加载模块new Module()。

1
2
var module = new Module(filename, parent);
Module._cache[filename] = module;

4、执行 module.load(filename);

1
2
3
4
5
6
7
8
9
try {
module.load(filename);
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}

5、module.load主要执行Module._extensions [extension] (this, filename)

1
2
3
4
5
6
7
8
9
10
11
12
13
Module.prototype.load = function(filename) {
debug('load ' + JSON.stringify(filename) +
' for module ' + JSON.stringify(this.id));
assert(!this.loaded);
this.filename = filename;
this.paths = Module._nodeModulePaths(path.dirname(filename));
var extension = path.extname(filename) || '.js';
if (!Module._extensions[extension]) extension = '.js';
Module._extensions[extension](this, filename);
this.loaded = true;
};

6、require 返回的是 module.exports
module.exports 默认是空对象
是在 Module._extensions 这个函数里面扩展的
分三种情况 js json node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Native extension for .js
Module._extensions['.js'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
module._compile(stripBOM(content), filename);
};
// Native extension for .json
Module._extensions['.json'] = function(module, filename) {
var content = fs.readFileSync(filename, 'utf8');
try {
// 以json为例 `module.exports =` 赋值步骤
module.exports = JSON.parse(stripBOM(content));
} catch (err) {
err.message = filename + ': ' + err.message;
throw err;
}
};
//Native extension for .node
Module._extensions['.node'] = process.dlopen;

end 最后返回的是

1
return module.exports;

小测

1
2
3
4
5
// a.js
console.log('a')
var c = require('./c.js');
c.a = 8
module.exports= c
1
2
3
4
5
// b.js
var c = require('./c.js');
var a = require('./a.js');
console.log(c)
console.log(c === a)
1
2
3
4
// c.js
let c = {a:1}
module.exports= c
console.log('被require2次但是只log一次');

运行 node b.js 输出如下

1
2
3
4
c.js被require2次但是只log一次
a
{ a: 8 }
true

ps

  • 该Module._compile方法是同步执行的,所以对Module._load的调用只能等到这段代码运行结束.
    • this.loaded = true; 在 Module.prototype.load 方法最后一行