이 기사는 Nodejs의 모듈 시스템을 안내합니다. 도움이 되기를 바랍니다!
초기 JavaScript는 간단한 페이지 상호작용 로직을 구현하는 데 사용되었지만, 시대가 발전하면서 브라우저는 단순한 상호작용을 표시할 수 있을 뿐만 아니라 다양한 웹사이트도 빛을 발하기 시작했습니다. 다른 정적 언어에 비해 웹사이트가 복잡해지고 프런트엔드 코드가 늘어나면서 이름 충돌 등 JavaScript의 모듈성 부족이 드러나기 시작합니다. 따라서 프런트 엔드 코드의 유지 관리 및 관리를 용이하게 하기 위해 커뮤니티에서는 모듈식 사양을 정의하기 시작했습니다. 이 과정에서 CommonJS
, AMD
, CMD
, ES 모듈
과 같은 많은 모듈 사양이 등장했습니다. 이 글에서는 CommonJS
를 기반으로 Node
에 구현된 모듈화를 주로 설명합니다. CommonJS
, AMD
, CMD
, ES modules
,本文章主要讲解Node
中根据CommonJS
实现的模块化。
首先,在 Node 世界里,模块系统是遵守CommonJS
规范的,CommonJS
规范里定义,简单讲就是:
module
对象来代表一个模块的信息exports
用来导出模块对外暴露的信息require
CommonJS
사양, CommonJS를 준수합니다. code> 사양 정의, 간단히 말하면:
모듈
개체를 통해 모듈 정보를 나타냅니다.내보내기
를 사용하세요. 외부에 노출된 정보require
를 통해 모듈을 참조합니다. 핵심 모듈: fs, http, path 및 기타 모듈과 같은 모듈은 필요하지 않습니다. 런타임에 설치하려면 메모리에 이미 로드되어 있어야 합니다. [추천 학습: "
nodejs tutorial사용자 정의 모듈: 주로 절대 경로 또는 상대 경로를 통해 도입되는 파일 모듈을 말합니다. CommonJS
规范,我们先讲下module.exports
与exports
的区别。
首先,我们用个新模块进行一个简单验证
console.log(module.exports === exports); // true
可以发现,module.exports
和epxorts
实际上就是指向同一个引用变量。
demo1
// a模块 module.exports.text = 'xxx'; exports.value = 2; // b模块代码 let a = require('./a'); console.log(a); // {text: 'xxx', value: 2}
从而也就验证了上面demo1
中,为什么通过module.exports
和exports
添加属性,在模块引入时两者都存在, 因为两者最终都是往同一个引用变量上面进行属性的添加.根据该 demo, 可以得出结论: module.exports
和exports
指向同一个引用变量
demo2
// a模块 module.exports = { text: 'xxx' } exports.value = 2; // b模块代码 let a = require('./a'); console.log(a); // {text: 'xxx'}
上面的 demo 例子中,对module.exports
进行了重新赋值, exports
进行了属性的新增, 但是在引入模块后最终导出的是module.exports
定义的值, 可以得出结论: noed 的模块最终导出的是module.exports
, 而exports
仅仅是对 module.exports 的引用, 类似于以下代码:
exports = module.exports = {}; (function (exports, module) { // a模块里面的代码 module.exports = { text: 'xxx' } exports.value = 2; console.log(module.exports === exports); // false })(exports, module)
由于在函数执行中, exports 仅是对原module.exports
对应变量的一个引用,当对module.exports
进行赋值时,exports
对应的变量和最新的module.exports
并不是同一个变量
require
에 대해 이야기한 후 node_modules 디렉토리까지 반복적으로 검색하는 내용을 기록한 배열입니다. CommonJS
사양, 먼저 module.exports
와 exports
의 차이점에 대해 이야기해 보겠습니다. 🎜🎜먼저 새 모듈을 사용하여 간단한 검증을 수행합니다🎜Module.prototype.require = function(id) { // ... try { // 主要通过Module的静态方法_load加载模块 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) { // ... return cachedModule.exports; } // 查看是否是node自带模块, 如http,fs等, 是就直接返回 const mod = loadNativeModule(filename, request); if (mod && mod.canBeRequiredByUsers) return mod.exports; // 根据文件路径初始化一个模块 const module = cachedModule || new Module(filename, parent); // ... // 将该模块加入模块缓存中 Module._cache[filename] = module; if (parent !== undefined) { relativeResolveCache[relResolveCacheIdentifier] = filename; } // ... // 进行模块的加载 module.load(filename); return module.exports; };
module.exports
와 epxorts
가 실제로 동일한 참조 변수를 가리키는 것을 확인할 수 있습니다. 🎜🎜🎜demo1🎜🎜Module.prototype._compile = function(content, filename) { // ... const compiledWrapper = wrapSafe(filename, content, this); // result = compiledWrapper.call(thisValue, exports, require, module, filename, dirname); // ... return result; };
demo1
에서 속성이 모듈의 module.exports
및 exports
를 통해 추가되는 이유 둘 다 존재합니다. 둘 다 궁극적으로 동일한 참조 변수에 속성을 추가하기 때문에 이 데모를 기반으로 결론을 내릴 수 있습니다. module.exports
및 exports</ code>는 동일한 항목을 가리킵니다. 참조 변수🎜🎜🎜demo2🎜🎜<div class="code" style="position:relative; padding:0px; margin:0px;"><div class="code" style="position:relative; padding:0px; margin:0px;"><pre class="brush:js;toolbar:false;">function wrapSafe(filename, content, cjsModuleInstance) {
// ...
const wrapper = Module.wrap(content);
// ...
}
let wrap = function(script) {
return Module.wrapper[0] + script + Module.wrapper[1];
};
const wrapper = [
&#39;(function (exports, require, module, __filename, __dirname) { &#39;,
&#39;\n});&#39;
];
ObjectDefineProperty(Module, &#39;wrap&#39;, {
get() {
return wrap;
},
set(value) {
patched = true;
wrap = value;
}
});</pre><div class="contentsignin">로그인 후 복사</div></div><div class="contentsignin">로그인 후 복사</div></div>🎜위 데모 예시에서 <code>module.exports
가 재할당되고, exports
에 새로운 속성이 늘어나는데, 모듈이 도입된 후에는 module.exports
에 의해 정의된 값이 최종적으로 내보내지는 반면, noed 모듈은 최종적으로 module.exports
를 내보내는 반면, exports
는 단지 내보내기만 한다는 결론을 내릴 수 있습니다. 다음 코드와 유사한 module.exports에 대한 참조: 🎜// test.mjs export default { a: 'xxx' } // import.js import a from './test.mjs'; console.log(a); // {a: 'xxx'}
module.exports
의 해당 변수에 대한 참조일 뿐입니다. code>module.exports, exports
에 해당하는 변수는 최신 module.exports
🎜require
모듈을 도입하는 과정은 크게 다음 단계로 나누어집니다.🎜.js
,.json
, .node
后缀的文件)Module.prototype.require = function(id) { // ... try { // 主要通过Module的静态方法_load加载模块 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) { // ... return cachedModule.exports; } // 查看是否是node自带模块, 如http,fs等, 是就直接返回 const mod = loadNativeModule(filename, request); if (mod && mod.canBeRequiredByUsers) return mod.exports; // 根据文件路径初始化一个模块 const module = cachedModule || new Module(filename, parent); // ... // 将该模块加入模块缓存中 Module._cache[filename] = module; if (parent !== undefined) { relativeResolveCache[relResolveCacheIdentifier] = filename; } // ... // 进行模块的加载 module.load(filename); return module.exports; };
至此, node 的模块原理流程基本过完了。目前 node v13.2.0 版本起已经正式支持 ESM 特性。
在接触 node 中,你是否会困惑 __filename
, __dirname
是从哪里来的, 为什么会有这些变量呢? 仔细阅读该章节,你会对这些有系统性的了解。
Module.prototype._compile = function(content, filename) { // ... const compiledWrapper = wrapSafe(filename, content, this); // result = compiledWrapper.call(thisValue, exports, require, module, filename, dirname); // ... return result; };
function wrapSafe(filename, content, cjsModuleInstance) { // ... const wrapper = Module.wrap(content); // ... } let wrap = function(script) { return Module.wrapper[0] + script + Module.wrapper[1]; }; const wrapper = [ '(function (exports, require, module, __filename, __dirname) { ', '\n});' ]; ObjectDefineProperty(Module, 'wrap', { get() { return wrap; }, set(value) { patched = true; wrap = value; } });
综上, 也就是之所以模块里面有__dirname
,__filename
, module
, exports
, require
这些变量, 其实也就是 node 在执行过程传入的, 看完是否解决了多年困惑的问题^_^
package.json
增加"type": "module"配置// test.mjs export default { a: 'xxx' } // import.js import a from './test.mjs'; console.log(a); // {a: 'xxx'}
较明显的区别是在于执行时机:
import
导入的模块会先进行预解析处理, 先于模块内的其他模块执行// entry.js console.log('execute entry'); let a = require('./a.js') console.log(a); // a.js console.log('-----a--------'); module.exports = 'this is a'; // 最终输出顺序为: // execute entry // -----a-------- // this is a
// entry.js console.log('execute entry'); import b from './b.mjs'; console.log(b); // b.mjs console.log('-----b--------'); export default 'this is b'; // 最终输出顺序为: // -----b-------- // execute entry // this is b
if
代码块中),如果需要动态引入, 需要使用import()
动态加载;ES 模块对比 CommonJS 模块, 还有以下的区别:
没有 require
、exports
或 module.exports
在大多数情况下,可以使用 ES 模块 import 加载 CommonJS 模块。(CommonJS 模块文件后缀为 cjs)
如果需要引入.js
后缀的 CommonJS 模块, 可以使用module.createRequire()
在 ES 模块中构造require
函数
// test.cjs export default { a: 'xxx' } // import.js import a from './test.cjs'; console.log(a); // {a: 'xxx'}
// test.cjs export default { a: 'xxx' } // import.js import a from './test.cjs'; console.log(a); // {a: 'xxx'}
// test.cjs export default { a: 'xxx' } // import.mjs import { createRequire } from 'module'; const require = createRequire(import.meta.url); // test.js 是 CommonJS 模块。 const siblingModule = require('./test'); console.log(siblingModule); // {a: 'xxx'}
没有 __filename 或 __dirname
这些 CommonJS 变量在 ES 模块中不可用。
没有 JSON 模块加载
JSON 导入仍处于实验阶段,仅通过 --experimental-json-modules 标志支持。
没有 require.resolve
没有 NODE_PATH
没有 require.extensions
没有 require.cache
在 CommonJS 中引入 ES 模块
由于 ES Modules 的加载、解析和执行都是异步的,而 require() 的过程是同步的、所以不能通过 require() 来引用一个 ES6 模块。
ES6 提议的 import() 函数将会返回一个 Promise,它在 ES Modules 加载后标记完成。借助于此,我们可以在 CommonJS 中使用异步的方式导入 ES Modules:
// b.mjs export default 'esm b' // entry.js (async () => { let { default: b } = await import('./b.mjs'); console.log(b); // esm b })()
在 ES 模块中引入 CommonJS
在 ES6 模块里可以很方便地使用 import 来引用一个 CommonJS 模块,因为在 ES6 模块里异步加载并非是必须的:
// a.cjs module.exports = 'commonjs a'; // entry.js import a from './a.cjs'; console.log(a); // commonjs a
至此,提供 2 个 demo 给大家测试下上述知识点是否已经掌握,如果没有掌握可以回头再进行阅读。
demo module.exports&exports
// a模块 exports.value = 2; // b模块代码 let a = require('./a'); console.log(a); // {value: 2}
demo module.exports&exports
// a模块 exports = 2; // b模块代码 let a = require('./a'); console.log(a); // {}
require&_cache 模块缓存机制
// origin.js let count = 0; exports.addCount = function () { count++ } exports.getCount = function () { return count; } // b.js let { getCount } = require('./origin'); exports.getCount = getCount; // a.js let { addCount, getCount: getValue } = require('./origin'); addCount(); console.log(getValue()); // 1 let { getCount } = require('./b'); console.log(getCount()); // 1
根据上述例子, 模块在 require 引入时会加入缓存对象require.cache
中。 如果需要删除缓存, 可以考虑将该缓存内容清除,则下次require
模块将会重新加载模块。
let count = 0; exports.addCount = function () { count++ } exports.getCount = function () { return count; } // b.js let { getCount } = require('./origin'); exports.getCount = getCount; // a.js let { addCount, getCount: getValue } = require('./origin'); addCount(); console.log(getValue()); // 1 delete require.cache[require.resolve('./origin')]; let { getCount } = require('./b'); console.log(getCount()); // 0
至此,本文主要介绍了 Node 中基于CommonJS
实现的模块化机制,并且通过源码的方式对模块化的整个流程进行了分析,有关于模块的介绍可查看下面参考资料。有疑问的欢迎评论区留言,谢谢。
author : sloweat 더 많은 프로그래밍 관련 지식을 보려면 다음을 방문하십시오. !
위 내용은 한 기사로 Nodejs의 모듈 시스템을 빠르게 이해하세요의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!