nodejs module.export require 原理分析

   大部分实现在module.js中,按照顺序来看: 首先调用require('xx')的时候内部调用了Module._load(path, parent) :

 
 
  1. Module.prototype.require = function(path) {
  2. assert(path, 'missing path');
  3. assert(util.isString(path), 'path must be a string');
  4. return Module._load(path, this);
  5. };
       至于我们平时调用的require方法是不是就是这个Module原型上的require在稍后就可以确认。 

      _load函数里处理了模块的缓存逻辑,这个大家知道下就行,接下来主要是这段逻辑

 
 
  1. var module = new Module(filename, parent);
  2. if (isMain) {
  3. process.mainModule = module;
  4. module.id = '.';
  5. }
  6. Module._cache[filename] = module;
  7. var hadException = true;
  8. try {
  9. module.load(filename);
  10. hadException = false;
  11. } finally {
  12. if (hadException) {
  13. delete Module._cache[filename];
  14. }
  15. }
  16. return module.exports;

  17.  

    可以看到try块中调用了module.load(file)方法,然后调用完就返回了module对象的exports属性,那继续查看load(file)方法是怎么加载这个file并且赋值给module.exports。

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

     直接看倒数4行,这几行就是根据要require的文件后缀来进行不同方法分发,我们这里就看最常见的对js文件的处理逻辑,其他后缀还支持'.node','.json'等。   

 
 
  1. // Native extension for .js
  2. Module._extensions['.js'] = function(module, filename) {
  3. var content = fs.readFileSync(filename, 'utf8');
  4. module._compile(stripBOM(content), filename);
  5. };

    对.js文件,就是直接读取了这个文件的文本内容做一些字符处理,然后调用_compile 方法,好了,马上就到终点了,直接看_compile(content,filename)方法。

     _compile方法内容较长,但大部分不用特别深究,不影响理解require的逻辑。大致做了以下事情: 构建一个require变量


 
 
  1. Module.prototype._compile = function(content, filename) {
  2. var self = this;
  3. // remove shebang
  4. content = content.replace(/^\#\!.*/, '');
  5. function require(path) {
  6. return self.require(path);
  7. }
  8. require.resolve = function(request) {
  9. return Module._resolveFilename(request, self);
  10. };
  11. Object.defineProperty(require, 'paths', { get: function() {
  12. throw new Error('require.paths is removed. Use ' +
  13. 'node_modules folders, or the NODE_PATH ' +
  14. 'environment variable instead.');
  15. }});
  16. require.main = process.mainModule;
  17. // Enable support to add extra extension types
  18. require.extensions = Module._extensions;
  19. require.registerExtension = function() {
  20. throw new Error('require.registerExtension() removed. Use ' +
  21. 'require.extensions instead.');
  22. };
  23. require.cache = Module._cache;


    还记得一开始的问题么,这个require对象就是Module.require,其实这个require对象就是我们平时调用的require('xxx')所用的那个。

      然后有一段根据配置决定模块加载方式的,可以先不用看,因为默认不走这段逻辑,未避免分散注意力,这里就不贴了,有兴趣可以自己看环境变量:NODE_MODULE_CONTEXTS。

       接下来才终于到了真正要进行动手装载模块的时候了,没几行代码:

 
 
  1. var wrapper = Module.wrap(content);

    这个wrap方法调用的是NativeModule.wrap方法,至于NativeModule.wrap方法究竟是干嘛的可看node.js源码:     

 
 
  1. NativeModule.wrap = function(script) {
  2. return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];
  3. };
  4. NativeModule.wrapper = [
  5. '(function (exports, require, module, __filename, __dirname) { ',
  6. '\n});'
  7. ];

      这个wrap方法非常重要!,直接解释了require核心是怎么实现的,其实就是把require的那个js文件的代码文本包在一个匿名函数体里。再看下这个自匿名函数的参数,也就是每个js模块中能直接访问到的变量。

       接下去看:

 
 
  1. var compiledWrapper = runInThisContext(wrapper, { filename: filename });
      这里调用了vm模块的runInThisContext,注意,这里并没有执行模块的代码哦,这里只是执行了wrap方法,这句运行完其实只是获得了一个匿名函数对象(之前只是代码文本)。

       接下去才是真正执行这个匿名函数。

 
 
  1. var args = [self.exports, require, self, filename, dirname];
  2. return compiledWrapper.apply(self.exports, args);

       对应下这里的参数,这里可以很简单发现,首先每个模块的this就是module.exports(apply的第一个参数), 其次exports就是module对象上的exports,同一个东西。所以这里可以直接弄明白exports和module的关系了,可以直接在exports上加东西,或者直接换一个。然后传入的require就是_compile方法刚开始创建的那个require方法,执行逻辑是一致的,所以提供了模块加载的传递性。 所以在每个模块中直接去修改module对象上的exports就决定了最终模块返回的结果。

       总结下,上面介绍的过程主要是最常见的加载非本地js模块的逻辑流程。根据代码可以知道每个模块中可以访问到的5个变量。了解了这些后对node中基本的module加载应该没不存在其他很大的疑惑了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值