>웹 프론트엔드 >JS 튜토리얼 >Node.js의 모듈 시스템 이해

Node.js의 모듈 시스템 이해

青灯夜游
青灯夜游앞으로
2020-11-24 17:58:153290검색

Node.js의 모듈 시스템 이해

관련 추천: "node js tutorial"

Node.js

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

模块对外暴露接口使用 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')

模块类型

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/.node_modules
  • $HOME/.node_libraries
  • $PREFIX/lib/node

其中 $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를 사용하며 외부 노출 면적 방법을 사용하여 원의 면적을 계산합니다🎜

module.exports🎜🎜모듈의 외부에 노출된 인터페이스는 module.exports를 사용합니다. 두 가지 일반적인 사용법이 있습니다: 새 객체에 속성을 추가하거나 값을 할당합니다🎜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">{   &quot;presets&quot;: [     [&quot;@babel/preset-env&quot;, {       &quot;targets&quot;: {         &quot;node&quot;: &quot;8.9.0&quot;,         &quot;esmodules&quot;: 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) {
	// 模块的代码实际上在这里
});
  • __filename:当前模块文件的绝对路径
  • __dirname:当前模块文件据所在目录的绝对路径
  • module:当前的模块实例
  • require:加载其它模块的方法,module.require 的快捷方式
  • exports:导出模块接口的对象,module.exports 的快捷方式

回头看看最开始的问题,为什么 exports 对象不支持赋值为其它对象?把上面函数添加一句 exports 对象来源就很简单了

const exports = module.exports;
(function(exports, require, module, __filename, __dirname) {
	// 模块的代码实际上在这里
});

其它模块 require 到的肯定是模块的 module.exports 对象,如果吧 exports 对象赋值给其它对象,就和 module.exports 对象断开了连接,自然就没用了

在 Node.js 中使用 ES Module

随着 ES6 使用越来越广泛,Node.js 也支持了 ES6 Module,有几种方法

babel 构建

使用 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제