您的位置:首页 > Web前端 > Node.js

nodejs module.export require 原理分析

2017-01-10 14:07 766 查看
   大部分实现在module.js中,按照顺序来看: 首先调用require('xx')的时候内部调用了Module._load(path, parent) :
Module.prototype.require = function(path) {

assert(path, 'missing path');

assert(util.isString(path), 'path must be a string');

return Module._load(path, this);

};


       至于我们平时调用的require方法是不是就是这个Module原型上的require在稍后就可以确认。 

      _load函数里处理了模块的缓存逻辑,这个大家知道下就行,接下来主要是这段逻辑
var module = new Module(filename, parent);


if (isMain) {

process.mainModule = module;

module.id = '.';

}


Module._cache[filename] = module;


var hadException = true;


try {

module.load(filename);

hadException = false;

} finally {

if (hadException) {

delete Module._cache[filename];

}

} 


return module.exports;

 

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

};


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

Module._extensions['.js'] = function(module, filename) {

var content = fs.readFileSync(filename, 'utf8');

module._compile(stripBOM(content), filename);

};


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

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

Module.prototype._compile = function(content, filename) {

var self = this;

// remove shebang

content = content.replace(/^\#\!.*/, '');


function require(path) {

return self.require(path);

}


require.resolve = function(request) {

return Module._resolveFilename(request, self);

};


Object.defineProperty(require, 'paths', { get: function() {

throw new Error('require.paths is removed. Use ' +

'node_modules folders, or the NODE_PATH ' +

'environment variable instead.');

}});


require.main = process.mainModule;


// Enable support to add extra extension types

require.extensions = Module._extensions;

require.registerExtension = function() {

throw new Error('require.registerExtension() removed. Use ' +

'require.extensions instead.');

};


require.cache = Module._cache;


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

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

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

var wrapper = Module.wrap(content);


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

return NativeModule.wrapper[0] + script + NativeModule.wrapper[1];

};


NativeModule.wrapper = [

'(function (exports, require, module, __filename, __dirname) { ',

'\n});'

];


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

       接下去看:
var compiledWrapper = runInThisContext(wrapper, { filename: filename });


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

       接下去才是真正执行这个匿名函数。
var args = [self.exports, require, self, filename, dirname];

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加载应该没不存在其他很大的疑惑了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: