관련 추천: "node js tutorial"
JavaScript의 모듈은 웹 페이지에 대화형 기능을 추가하기 위한 간단한 스크립트 언어로 나왔습니다. 처음에는 모듈 시스템이 포함되어 있지 않았습니다. JavaScript는 점점 더 복잡해지는 문제를 해결합니다. 모든 코드를 하나의 파일에 작성하고 기능 단위를 구별하는 기능을 사용하면 더 이상 복잡한 애플리케이션 개발을 지원할 수 없습니다. ES6은 대부분의 고급 언어에 공통적인 클래스와 모듈을 제공하므로 개발자가 더 쉽게 구성할 수 있습니다.
import _ from 'lodash'; class Fun {} export default Fun;
위 세 줄의 코드는 모듈 시스템의 가장 중요한 두 가지 요소인 가져오기와 내보내기를 보여줍니다.
내보내기
는 모듈의 외부 인터페이스를 지정하는 데 사용됩니다export
用于规定模块的对外接口
import
用于输入其他模块提供的功能
而在 ES6 之前,社区出现了很多模块加载方案,最主要的有 CommonJS 和 AMD 两种,Node.js 诞生早于 ES6,模块系统使用的是类似 CommonJS 的实现,遵从几个原则
一个文件是一个模块,文件内的变量作用域都在模块内
使用 module.exports
对象导出模块对外接口
使用 require
引入其它模块
circle.js
const { PI } = Math; module.exports = function area(r) { PI * r ** 2; };
上面代码就实现了 Node.js 的一个模块,模块没有依赖其它模块,导出了方法 area
计算圆的面积
test.js
const area = require('./circle.js'); console.log(`半径为 4 的圆的面积是 ${area(4)}`);
模块依赖了 circle.js,使用其对外暴露的 area 方法,计算圆的面积
模块对外暴露接口使用 module.exports,常见的有两种用法:为其添加属性或赋值到新对象test.js
// 添加属性 module.exports.prop1 = xxx; module.exports.funA = xxx; module.exports.funB = xxx; // 赋值到全新对象 module.exports = { prop1, funA, funB, };
两种写法是等价的,使用时候没区别
const mod = require('./test.js'); console.log(mod.prop1); console.log(mod.funA());
还有另外一种直接使用 exports
对象的方法,但是只能对其添加属性,不能赋值到新对象,后面会介绍原因
// 正确的写法:添加属性 exports.prop1 = xxx; exports.funA = xxx; exports.funB = xxx; // 赋值到全新对象 module.exports = { prop1, funA, funB, };
require 用法比较简单,id 支持模块名和文件路径两种类型
const fs = require('fs'); const _ = require('lodash');
示例中的 fs、lodash 都是模块名,fs 是 Node.js 内置的核心模块,lodash 是通过 npm 安装到 node_modules
下的第三方模块,如果出现重名,优先使用系统内置模块
因为一个项目内可能会包含多个 node_modules 文件夹(Node.js 比较失败的设计),第三方模块查找过程会遵循就近原则逐层上溯(可以在程序中打印 module.paths
查看具体查找路径),直到根据 NODE_PATH
环境变量查找到文件系统根目录,具体过程可以参考官方文档
此外,Node.js 还会搜索以下的全局目录列表:
其中 $HOME
是用户的主目录, $PREFIX
是 Node.js 里配置的 node_prefix
。强烈建议将所有的依赖放在本地的 node_modules 目录,这样将会更快地加载,且更可靠
模块还可以可以使用文件路径加载,这是项目内自定义模块的通用加载方式,路径可以省略拓展名,会按照 .js、.json、.node 顺序尝试
'/'
为前缀的模块是文件的绝对路径,按照系统路径查找模块'./'
为前缀的模块是相对于当前调用 require 方法的文件,不受后续模块在哪里被使用到影响模块在第一次加载后会被缓存到 Module._cache
,如果每次调用 require('foo')
都解析到同一文件,则返回相同的对象,同时多次调用 require(foo)
不会导致模块的代码被执行多次。 Node.js 根据实际的文件名缓存模块,因此从不同层级目录引用相同模块不会重复加载。
理解的模块单次加载机制方便我们理解模块循环依赖后的现象a.js
console.log('a 开始'); exports.done = false; const b = require('./b.js'); console.log('在 a 中,b.done = %j', b.done); exports.done = true; console.log('a 结束');
b.js
console.log('b 开始'); exports.done = false; const a = require('./a.js'); console.log('在 b 中,a.done = %j', a.done); exports.done = true; console.log('b 结束');
main.js
li>
import
는 다른 모듈에서 제공하는 기능을 가져오는 데 사용됩니다
ES6 이전에는 커뮤니티에 많은 모듈 로딩 솔루션이 등장했는데 가장 중요한 솔루션은 CommonJS와 AMD와 Node.js가 탄생했습니다. ES6 이전에는 모듈 시스템이 CommonJS와 유사한 구현을 사용하여 여러 원칙을 따릅니다
파일은 모듈이고 파일의 변수 범위는 모듈 내에 있습니다
🎜🎜module .exports
객체 내보내기 모듈 외부 인터페이스 사용🎜🎜🎜require
를 사용하여 다른 모듈 도입🎜 🎜circle.js
🎜console.log('main 开始'); const a = require('./a.js'); const b = require('./b.js'); console.log('在 main 中,a.done=%j,b.done=%j', a.done, b.done);🎜위 코드는 Node.js의 모듈을 구현한 것입니다. 이 모듈은 다른 모듈에 의존하지 않으며
area
메소드를 내보내서 면적을 계산합니다. 원🎜🎜test.js
🎜main 开始 a 开始 b 开始 在 b 中,a.done = false b 结束 在 a 中,b.done = true a 结束 在 main 中,a.done=true,b.done=true🎜모듈은 Circle.js를 사용하며 외부 노출 면적 방법을 사용하여 원의 면적을 계산합니다🎜
test.js
🎜(function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });🎜둘 작성 방법은 동일하며 사용시 차이가 없습니다🎜
const exports = module.exports; (function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });🎜
exports 개체 방법을 직접 사용하는 방법도 있지만 속성만 추가할 수 있고 새 개체에 할당할 수는 없습니다. 이유는 나중에 소개하겠습니다🎜<pre class="brush:php;toolbar:false">{
"presets": [
["@babel/preset-env", {
"targets": {
"node": "8.9.0",
"esmodules": true
}
}]
]
}</pre>
<h2 id="requireid">require('id')🎜<h3 id="module type">Module type</h3>🎜require 사용법은 비교적 간단하며 id는 두 가지 유형을 지원합니다. 모듈 이름 및 파일 경로🎜<h4 id="모듈 이름">모듈 이름</h4>rrreee🎜fs와 예제의 lodash는 모두 모듈 이름입니다. fs는 Node.js에 내장된 핵심 모듈이고 lodash는 npm을 통해 <code>node_modules
아래에 설치된 타사 모듈. 중복된 이름이 있는 경우 프로젝트에 여러 node_modules 폴더가 포함될 수 있으므로 시스템 내장 모듈을 사용하는 것이 우선입니다(상대적으로 실패한 설계). Node.js), 타사 모듈 검색 프로세스는 근접성 원칙을 따르고 계층별로 올라갑니다(특정 검색 경로를 보려면 프로그램에서 module.paths
를 인쇄할 수 있음). NODE_PATH
환경 변수에 따라 파일 시스템 루트 디렉터리를 찾으세요. 특정 프로세스는 공식 문서를 참조하세요🎜🎜또한 Node.js는 다음 전역 디렉터리 목록도 검색합니다.🎜🎜$HOME/.node_modules 🎜$HOME/.node_libraries🎜$PREFIX/lib/node🎜여기서 $HOME
는 사용자의 홈 디렉터리인 $ PREFIX
는 Node.js에 구성된 node_prefix
입니다. 모든 종속성을 로컬 node_modules 디렉터리에 배치하는 것이 좋습니다. 이렇게 하면 더 빠르게 로드되고 더 안정적이 됩니다.🎜'/'
접두사가 붙은 모듈은 .js, .json, .node🎜🎜 파일의 절대 경로이며, 시스템 경로에 따라 모듈을 검색합니다.🎜 './'
접두사가 붙은 모듈은 현재 require 메소드를 호출하는 파일에 상대적이며, 후속 모듈이 사용되는 위치에 영향을 받지 않습니다.첫 번째 로드 ._cache
이후 모듈, require('foo')
에 대한 각 호출이 동일한 파일로 확인되면 동일한 객체가 반환되고 require(foo)
는 여러 번 호출됩니다. > 모듈의 코드가 여러 번 실행되지 않습니다. Node.js는 실제 파일 이름을 기반으로 모듈을 캐시하므로 다른 디렉터리 수준에서 참조할 때 동일한 모듈이 두 번 로드되지 않습니다. 🎜🎜잘 알려진 모듈 단일 로딩 메커니즘은 모듈 순환 종속성 현상에 대한 이해를 돕습니다🎜a.js
🎜rrreee🎜b.js
🎜rrreee🎜 main.js
:🎜rrreee🎜main.js가 a.js를 로드하면 a.js는 b.js를 로드합니다. 이때 b.js는 무한 루프를 방지하기 위해 a.js를 로드하려고 합니다. , a.js의 내보내기 개체의 🎜미완성 복사본🎜이 b.js 모듈에 제공된 다음 b.js가 로드를 완료하고 내보내기 개체를 a.js 모듈🎜🎜에 제공하므로 다음의 출력이 반환됩니다. 예는 🎜main 开始 a 开始 b 开始 在 b 中,a.done = false b 结束 在 a 中,b.done = true a 结束 在 main 中,a.done=true,b.done=true
看不懂上面的过程也没关系,日常工作根本用不到,即使看懂了也不要在项目中使用循环依赖!
Node.js 每个文件都是一个模块,模块内的变量都是局部变量,不会污染全局变量,在执行模块代码之前,Node.js 会使用一个如下的函数封装器将模块封装
(function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });
回头看看最开始的问题,为什么 exports 对象不支持赋值为其它对象?把上面函数添加一句 exports 对象来源就很简单了
const exports = module.exports; (function(exports, require, module, __filename, __dirname) { // 模块的代码实际上在这里 });
其它模块 require 到的肯定是模块的 module.exports 对象,如果吧 exports 对象赋值给其它对象,就和 module.exports 对象断开了连接,自然就没用了
随着 ES6 使用越来越广泛,Node.js 也支持了 ES6 Module,有几种方法
使用 babel 构建是在 v12 之前版本最简单、通用的方式,具体配置参考 @babel/preset-env(https://babeljs.io/docs/en/babel-preset-env)
.babelrc
{ "presets": [ ["@babel/preset-env", { "targets": { "node": "8.9.0", "esmodules": true } }] ] }
在 v12 后可以使用原生方式支持 ES Module
开启 --experimental-modules
模块名修改为 .mjs
(强烈不推荐使用)或者 package.json 中设置 "type": module
这样 Node.js 会把 js 文件都当做 ES Module 来处理,更多详情参考官方文档(https://nodejs.org/dist/latest-v13.x/docs/api/esm.html)
更多编程相关知识,请访问:编程视频!!
위 내용은 Node.js의 모듈 시스템 이해의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!