Javascript Modularize 2nd

NodeJS Module require

require实现了CommonJS标准,在Node中作为一个全局的函数来支持模块管理。
但是开发中遇到了一个有意思的场景:

假如ModuleA和ModuleB同时依赖一个第三方库,简单起见,设为var Foo = {request: 'foo'}

现在ModuleA引入Foo.js

ModuleA
1
2
var Foo = require('Foo');
Foo.request = 'bar';

ModuleB中也引入Foo.js

ModuleB
1
2
var Foo = require('Foo');
console.log(Foo.request); //bar

结果是这个对象的属性被改变了!这不就相当于全局变量了吗?

不过,据此可以推测NodeJS是如何实现require的:

  1. 内部把依赖的module用function包装了一遍,使得require的变量变成私有的;
  2. 内部使用缓存机制,防止重复加载相同的JS文件。
module.jsNodeJs
1
2
3
4
5
Module.prototype.require = function(path) {
assert(util.isString(path), 'path must be a string');
assert(path, 'missing path');
return Module._load(path, this);
};

上面这段代码就是require源代码,直接进入_load:

Module._load
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
29
Module._load = function(request, parent, isMain) {
if (parent) {
debug('Module._load REQUEST ' + (request) + ' parent: ' + parent.id);
}

var filename = Module._resolveFilename(request, parent);

var cachedModule = Module._cache[filename];
if (cachedModule) {
return cachedModule.exports;
}
...
var module = new Module(filename, parent);
...
Module._cache[filename] = module;

var hadException = true;

try {
module.load(filename); //invoke extensions['.js|json'] -> _compile js
hadException = false;
} finally {
if (hadException) {
delete Module._cache[filename];
}
}

return module.exports;
};

我省略一些无关紧要的代码,仅从以上代码可以看出,确实有cache使用了,所以require到内存中的对象其实只有一个!

Module.prototype._compile
1
2
3
4
5
6
// create wrapper function
var wrapper = Module.wrap(content);
var compiledWrapper = runInThisContext(wrapper, { filename: filename });
...
var args = [self.exports, require, self, filename, dirname];
return compiledWrapper.apply(self.exports, args);

上面是一小段compile的代码,执行到这里的顺序是_load -> _extensions[‘.js’] -> _compile.
可以看到确实进行了wrap操作,将js文件中的内容wrap成一个function, 最后传入self.exports (module.exports),这样module.exports便继承了原JS执行的结果。