从源码分析Node之require

xiaoxiao2021-02-28  104

关于Node中Module这里不多说了(node把每一个文件都看成一个Module 的实例),只看整个分析流程

本文这只是一种情况,如果是引用node内部模块则是有些不同的流程 require开始

//E:\\SVNPRO\node_modules\***\bin\proxy\index.js require('util/nodeVersion.js')

这里执行之后会走到

function require(path) { try { exports.requireDepth += 1; return self.require(path); } finally { exports.requireDepth -= 1; } }

requireDepth指的是引用的深度,重点是self.require(path)

Module.prototype.require = function(path) { assert(path, 'missing path'); assert(typeof path === 'string', 'path must be a string'); return Module._load(path, this, /* isMain */ false); };

以上全部的path在该例子中值都是**‘util/nodeVersion.js’**,到了Module._load(path, this, /* isMain */ false)

重点来了,这个函数有点长

Module._load = function(request, parent, isMain) { if (parent) { debug('Module._load REQUEST %s parent: %s', request, parent.id); } var filename = Module._resolveFilename(request, parent, isMain); var cachedModule = Module._cache[filename]; if (cachedModule) { return cachedModule.exports; } if (NativeModule.nonInternalExists(filename)) { debug('load native module %s', request); return NativeModule.require(filename); } var module = new Module(filename, parent); if (isMain) { process.mainModule = module; module.id = '.'; } Module._cache[filename] = module; tryModuleLoad(module, filename); return module.exports; };

这里的request是**‘util/nodeVersion.js’**,parent是指最开始执行 require('util/nodeVersion.js')代码的文件,就是index.js,也是一个module(模块),parent属性的值是

顺便说一下Module,每一个module实例都有上图这些属性 children 数组:是该module require了哪些模块 exports:就是该文件(模块)开放给外部调用接口,module.exports=somethingToexports中的somethingToexports loaded:是否已经被加载过,加载过的模块会添加到Module._cache中 parent:require过该模块的模块 paths:指所有有可能包含该模块的路径,通常是从当前目录到根目录每一级目录下的node_modules文件夹 resolveFilenameCache:指的是index.js中前面require过的已经引入的模块,每引入一个模块都会把它加到这个属性中,后面会看到

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

这一句是根据模块名和parent来获取到该模块的绝对地址

新开一个分支来看 Module._resolveFilename (sea-node.js)

//sea-node.js Module._resolveFilename = function(request, parent) { var res; //request = request.replace(/\?.*$/, '') // remove timestamp etc. //性能优化 if(parent.resolveFilenameCache){ if(parent.resolveFilenameCache[request]){ return parent.resolveFilenameCache[request] } }else{ parent.resolveFilenameCache = {}; } res = _resolveFilename(request, parent); parent.resolveFilenameCache[request] = res; return res; }

该函数中,先检查parent中resolveFilenameCache中是否包含要引入的模块(就是request参数),如果有说明之前require过,直接返回即可 如果没有,执行_resolveFilename(request, parent);,这个函数和上面的函数名字一样,但是在不同文件,不是一个函数

再下一层分支看 Module._resolveFilename (module.js)

//module.js Module._resolveFilename = function(request, parent, isMain) { if (NativeModule.nonInternalExists(request)) { return request; } var resolvedModule = Module._resolveLookupPaths(request, parent); var id = resolvedModule[0]; var paths = resolvedModule[1]; // look up the filename first, since that's the cache key. debug('looking for %j in %j', id, paths); var filename = Module._findPath(request, paths, isMain); if (!filename) { var err = new Error("Cannot find module '" + request + "'"); err.code = 'MODULE_NOT_FOUND'; throw err; } return filename; };

Module._resolveLookupPaths 函数是利用parent和request来得到所有可能的存放该模块的地址(如C盘下的.node_modules),例如本例中返回的resolvedModule值为 数组第一个元素是request值,第二个元素是parent.paths和其他可能的存放模块的地址的组合 Module._findPath函数做的事对paths数组进行遍历,并对可能的文件(.js .json .node)后缀都进行测试,直到找到这个文件,返回的filename是该模块的绝对路径

之后这个函数运行结束,跳出来到上一个_resolveFilename(sea-node.js)中只剩最后一句parent.resolveFilenameCache[request] = res;,这是之前说过的,找到该模块后就把它添加到parent的resolveFilenameCache中,说明已经引用过。 这样再上一层到 _load 函数中,下面是按照顺序寻找该模块:

Module._cache:缓存中NativeModule:Node的“土著”模块(自带的,例如http,fs等)上面两个都没有,再新建模块 var module = new Module(filename, parent);,这时候module.exports里面还啥都没有,还并没有读取文件内容。

Module._cache[filename] = module;这一句代码则是将该模块加到缓存中去,以便下次可以直接从缓存中获取。

重点来了!!!

tryModuleLoad(module, filename);这个函数包含了读取该模块文件内容;执行该函数会进入Module.load()中

Module.prototype.load = function(filename) { debug('load %j for module %j', filename, 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; };

通过path.extname()获取该模块文件的扩展名,Module._extesions是一个Object,包含对应每一个后缀文件的处理函数(.js .json .node 三种),这里是js文件所以我们直接进入该函数看

Module._extensions['.js'] = function(module, filename) { var content = fs.readFileSync(filename, 'utf8'); module._compile(internalModule.stripBOM(content), filename); };

先读取文件内容,然后调用module._compile处理content

Module.prototype._compile = function(content, filename) { // Remove shebang var contLen = content.length; if (contLen >= 2) { if (content.charCodeAt(0) === 35/*#*/ && content.charCodeAt(1) === 33/*!*/) { if (contLen === 2) { // Exact match content = ''; } else { // Find end of shebang line and slice it off var i = 2; for (; i < contLen; ++i) { var code = content.charCodeAt(i); if (code === 10/*\n*/ || code === 13/*\r*/) break; } if (i === contLen) content = ''; else { // Note that this actually includes the newline character(s) in the // new output. This duplicates the behavior of the regular expression // that was previously used to replace the shebang line content = content.slice(i); } } } } // create wrapper function var wrapper = Module.wrap(content); var compiledWrapper = vm.runInThisContext(wrapper, { filename: filename, lineOffset: 0, displayErrors: true }); var dirname = path.dirname(filename); var require = internalModule.makeRequireFunction.call(this); var args = [this.exports, require, this, filename, dirname]; var depth = internalModule.requireDepth; if (depth === 0) stat.cache = new Map(); var result = compiledWrapper.apply(this.exports, args); if (depth === 0) stat.cache = null; return result; };

函数比较长,只复制了一部分,这里主要关注wrapper,这是对读取的js文件内容进行了一个“包装”,因为作为一个模块代码里面肯带会有module.exports,那么怎么把exports的内容传出来呢,其实他做的工作就是类似于: 文件内容:module.exports={name:"bob"} 在外面包装一层匿名函数:

(function(exports,require,module){ module.exports={name:"bob"} })(module.exports,require,module);

这样执行完了里面exports的内容就传出来了。(回到源代码)

wrapper在content上加上头尾之后(这时候还是字符串,需要变成可以执行的js函数,例如eval的功能),经过处理(V8引擎处理的)后得到的compiledWrapper就是js函数,可以看下它执行的代码

var args = [this.exports, require, this, filename, dirname]; var result = compiledWrapper.apply(this.exports, args);

到这里就结束了,接口内容已经放到module.exports中了,最后loaded变成true代表该模块已加载过了

转载请注明原文地址: https://www.6miu.com/read-67694.html

最新回复(0)