We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
在Node.js模块系统中,每个文件都被视为一个独立的模块
在执行模块之前,Node.js会使用一个如下的封装器进行封装
(function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });
通过这样,实现了以下几点
module
exports
__filename
__dirname
module.exports
require.resolve.paths
运行一个文件时,require.main会被设为它的module 可以通过 require.main === module 来判断一个文件是否被直接运行
require.main
require.main === module
用来查询某个模块文件的带有完整绝对路径的文件名
// 输出 /home/user/test.js require.resolve('./test');
模块在第一次加载后,会被缓存,每次调用同个模块,返回相同的对象
模块是基于其解析的文件名进行缓存的。 由于调用模块的位置的不同,模块可能被解析成不同的文件名(比如从 node_modules 目录加载),这样就不能保证 require('foo') 总能返回完全相同的对象。
有些系统,区分大小写
被引入的模块将被缓存在这个对象中,从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块,不使用于原生插件。
require() 总是会优先加载核心模块。 例如, require('http') 始终返回内置的 HTTP 模块,即使有同名文件。
核心模块在node源代码的lib/目录下
lib/
参照实例代码的输出
当 main.js 加载 a.js 时, a.js 又加载 b.js。 此时, b.js 会尝试去加载 a.js。 为了防止无限的循环,会返回一个 a.js 的 exports 对象的未完成的副本(空对象)给 b.js 模块。 然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。
未完成的副本(空对象)
没有确切的文件名,会尝试带上.js,.json,.node拓展名再加载 .node文件会被解析为通过process.dlopen()加载的编译后的插件模块 当没有以 '/'、 './' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。
.js
.json
.node
process.dlopen()
package.json的main属性,指定一个模块,如果没有,则试图加载目录下的index.js或者index.node文件
package.json
main
index.js
index.node
会从当前目录的node_modules查找,然后以此往上找到文件系统的根目录
node_modules
require通过makeRequireFunction方法来创建
require
makeRequireFunction
// makeRequireFunction在global上定义,require === this.require ObjectDefineProperty(context, 'require', { configurable: true, writable: true, value: makeRequireFunction(module) }); // 从上面可以看出,mod就是module function makeRequireFunction(mod, redirects) { const Module = mod.constructor; let require; if (redirects) { // redirects的情况忽略 } else { // require的方法定义 require = function require(path) { // 调用的是module.require return mod.require(path); }; } // resolve方法 function resolve(request, options) { validateString(request, 'request'); return Module._resolveFilename(request, mod, false, options); } require.resolve = resolve; // path方法 function paths(request) { validateString(request, 'request'); return Module._resolveLookupPaths(request, mod); } resolve.paths = paths; // 在新版本中,不可自定义的extensions require.extensions = Module._extensions; // 模块缓存挂载 require.cache = Module._cache; return require; }
从上面可以得知,最后require的调用,是Module.prototype.require,最终调用的是Module._load
Module.prototype.require
Module._load
NativeModule.prototype.compileForPublicLoader()
// 直接调用的是Module._load Module.prototype.require = function(id) { // .. try { return Module._load(id, this, /* isMain */ false); } finally { /** .. */ } }; Module._load = function(request, parent, isMain) { let relResolveCacheIdentifier; // ... // 获取文件名 const filename = Module._resolveFilename(request, parent, isMain); // 获取缓存,如果有缓存的话,直接返回缓存 const cachedModule = Module._cache[filename]; if (cachedModule !== undefined) { // 把模块放到module.children中 updateChildren(parent, cachedModule, true); return cachedModule.exports; } // 优先读取原生模块,如果有原生模块且可以给用户调用,则暴露出去 const mod = loadNativeModule(filename, request, experimentalModules); if (mod && mod.canBeRequiredByUsers) return mod.exports; // 如果不是原生模块,通过new Module建立模块实例 const module = new Module(filename, parent); // 如果是主模块,绑定到process上 if (isMain) { process.mainModule = module; module.id = '.'; } // 把模块放到缓存中 Module._cache[filename] = module; // 存放文件名 if (parent !== undefined) { relativeResolveCache[relResolveCacheIdentifier] = filename; } let threw = true; try { if (enableSourceMaps) { try { // 最终调用module.load module.load(filename); } catch (err) { /** ... */ } } else { module.load(filename); } threw = false; } finally { /** ... */ } return module.exports; };
在这里可以看出
接下来看下load方法
load
Module.prototype.load = function(filename) { // 这里来获取文件地址以及它的扩展名 this.filename = filename; this.paths = Module._nodeModulePaths(path.dirname(filename)); const extension = findLongestRegisteredExtension(filename); // ... // 这里来读取文件,然后设置loaded为true,表示已经加载完毕 Module._extensions[extension](this, filename); this.loaded = true; // ... };
那么,从这里看出,load方法其实就是调用Module._extensions上对应拓展名的方法
Module._extensions
接下来,看下对应的方法是怎么去load的
js文件通过读取其文件,再进行compile。其中,_compile就是做了
Module._extensions['.js'] = function(module, filename) { if (filename.endsWith('.js')) { // .. } const content = fs.readFileSync(filename, 'utf8'); module._compile(content, filename); }; Module.prototype._compile = function(content, filename) { let moduleURL; let redirects; // .. // 这里生成一个编译方法 const compiledWrapper = wrapSafe(filename, content, this); var inspectorWrapper = null; // .. if (inspectorWrapper) { // .. } else { // 调用上面生成的编译方法 result = compiledWrapper.call(thisValue, exports, require, module, filename, dirname); } // .. return result; }; // 返回了编译后的方法 function wrapSafe(filename, content, cjsModuleInstance) { if (patched) { // 这里做外层的包裹 const wrapper = Module.wrap(content); // 通过vm,来生成把代码在当前上下文去执行 // wrapper,是一串代码字符串 return vm.runInThisContext(wrapper, { /** ... */ }); } // ... let compiled; try { // 调用原生模块做方法编译 compiled = compileFunction(); } catch (err) { /** ... */ } // .. return compiled.function; } // 这里,对读取的代码字符串,做了外层包裹,并把全局参数传入 let wrap = function(script) { return Module.wrapper[0] + script + Module.wrapper[1]; }; const wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ];
json文件较为简单,直接读取文件字符串,然后解析json
Module._extensions['.json'] = function(module, filename) { const content = fs.readFileSync(filename, 'utf8'); // 读取文件 // try { // 解析json module.exports = JSONParse(stripBOM(content)); } catch (err) { /** ... */ } };
The text was updated successfully, but these errors were encountered:
No branches or pull requests
module 模块
模块封装器
在Node.js模块系统中,每个文件都被视为一个独立的模块
在执行模块之前,Node.js会使用一个如下的封装器进行封装
通过这样,实现了以下几点
module
和exports
对象,以及__filename
和__dirname
等模块作用域
__dirname
获取当前模块的目录名__filename
当前模块的文件名的绝对路径exports
是module.exports
的简短引用形式module
对当前模块的引用require.resolve.paths
,返回一个数组,其中包含解析 request 过程中被查询的路径(核心模块返回null)主模块访问
运行一个文件时,
require.main
会被设为它的module
可以通过
require.main === module
来判断一个文件是否被直接运行require.resolve
用来查询某个模块文件的带有完整绝对路径的文件名
缓存
模块在第一次加载后,会被缓存,每次调用同个模块,返回相同的对象
注意事项
模块是基于其解析的文件名进行缓存的。 由于调用模块的位置的不同,模块可能被解析成不同的文件名(比如从 node_modules 目录加载),这样就不能保证 require('foo') 总能返回完全相同的对象。
有些系统,区分大小写
require.cache
被引入的模块将被缓存在这个对象中,从此对象中删除键值对将会导致下一次 require 重新加载被删除的模块,不使用于原生插件。
核心模块
require() 总是会优先加载核心模块。 例如, require('http') 始终返回内置的 HTTP 模块,即使有同名文件。
核心模块在node源代码的
lib/
目录下循环(重点)
参照实例代码的输出
当 main.js 加载 a.js 时, a.js 又加载 b.js。 此时, b.js 会尝试去加载 a.js。 为了防止无限的循环,会返回一个 a.js 的 exports 对象的
未完成的副本(空对象)
给 b.js 模块。 然后 b.js 完成加载,并将 exports 对象提供给 a.js 模块。文件模块
没有确切的文件名,会尝试带上
.js
,.json
,.node
拓展名再加载.node
文件会被解析为通过process.dlopen()
加载的编译后的插件模块当没有以 '/'、 './' 或 '../' 开头来表示文件时,这个模块必须是一个核心模块或加载自 node_modules 目录。
目录作为模块
package.json
的main
属性,指定一个模块,如果没有,则试图加载目录下的index.js
或者index.node
文件从 node_modules 目录加载
会从当前目录的
node_modules
查找,然后以此往上找到文件系统的根目录源码阅读
require
require
通过makeRequireFunction
方法来创建从上面可以得知,最后require的调用,是
Module.prototype.require
,最终调用的是Module._load
NativeModule.prototype.compileForPublicLoader()
然后返回暴露的对象在这里可以看出
接下来看下
load
方法那么,从这里看出,load方法其实就是调用
Module._extensions
上对应拓展名的方法接下来,看下对应的方法是怎么去load的
js文件的读取
js文件通过读取其文件,再进行compile。其中,_compile就是做了
json文件的读取
json文件较为简单,直接读取文件字符串,然后解析json
The text was updated successfully, but these errors were encountered: