NodeJS Module require
require实现了CommonJS标准,在Node中作为一个全局的函数来支持模块管理。
但是开发中遇到了一个有意思的场景:
假如ModuleA和ModuleB同时依赖一个第三方库,简单起见,设为var Foo = {request: 'foo'}
现在ModuleA引入Foo.js
ModuleA1 2
| var Foo = require('Foo'); Foo.request = 'bar';
|
ModuleB中也引入Foo.js
ModuleB1 2
| var Foo = require('Foo'); console.log(Foo.request);
|
结果是这个对象的属性被改变了!这不就相当于全局变量了吗?
不过,据此可以推测NodeJS是如何实现require的:
- 内部把依赖的module用function包装了一遍,使得require的变量变成私有的;
- 内部使用缓存机制,防止重复加载相同的JS文件。
module.jsNodeJs1 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._load1 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); hadException = false; } finally { if (hadException) { delete Module._cache[filename]; } }
return module.exports; };
|
我省略一些无关紧要的代码,仅从以上代码可以看出,确实有cache使用了,所以require到内存中的对象其实只有一个!
Module.prototype._compile1 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执行的结果。