Система модулей Node.js
Система модулей Node.js — одна из его ключевых возможностей, позволяющая разработчикам организовывать код в небольшие повторно используемые единицы, называемые модулями.
Модуль — это независимый файл, который инкапсулирует определённую функциональность и может быть импортирован и использован в других файлах.
Система модулей Node.js — важная основа для создания приложений Node.js, позволяющая разработчикам разбивать код на повторно используемые и удобные для сопровождения части.
Каждый модуль — это отдельный файл JavaScript, содержащий набор связанных функций, объектов или переменных. Таким образом, Node.js предоставляет структурированный подход к управлению большими приложениями, делая код более понятным и простым в сопровождении.
Каждый файл в Node.js может выступать в роли модуля и по умолчанию находится в независимой области видимости.
Система модулей следует спецификации CommonJS, но также поддерживает ES-модули.
Типы модулей
Node.js поддерживает следующие типы модулей:
- Встроенные модули: Модули, поставляемые с Node.js, такие как
fs、http、pathи т. д. - Пользовательские модули: Модули, созданные разработчиками.
- Сторонние модули: Модули, установленные через npm, такие как
express、lodashи т. д.
Импорт встроенных модулей
Встроенные модули — это модули, которые поставляются с Node.js и уже включены в среду при установке Node.js, поэтому дополнительная установка не требуется. Распространённые встроенные модули включают fs, http, path, os, crypto и т. д.
Чтобы импортировать встроенный модуль, нужно всего лишь использовать функциюrequire()и передать имя модуля. Например, чтобы импортировать модуль файловой системыfs、path、http, это можно сделать так:
// 导入文件系统模块
const fs = require('fs');
// 导入路径模块
const path = require('path');
// 导入 HTTP 模块
const http = require('http');Здесь fs — это строка, представляющая имя загружаемого модуля. Node.js автоматически распознаёт её как встроенный модуль и загружает соответствующую функциональность.
Пример
console.log('Операционная система:', os.platform());
console.log('Всего памяти:', os.totalmem());
Импорт сторонних модулей
Сторонние модули — это модули, опубликованные разработчиками или сообществом open-source, и их можно установить в проект через npm (Node Package Manager). Распространённые сторонние модули включают express, lodash, axios и т. д.
После установки сторонних модулей они помещаются в каталог node_modules проекта. Чтобы импортировать сторонний модуль, также используйте функциюrequire(), но передайте имя модуля. Например, чтобы импортировать фреймворк express, можно сделать так:
// 导入第三方模块 express
const express = require('express');
Перед импортом стороннего модуля убедитесь, что вы установили его через npm. Например, установку express можно выполнить следующей командой:
npm install express
Установите сторонний модуль lodash и импортируйте его для тестирования:
Пример
const array = [1, 2, 3, 4];
const reversedArray = lodash.reverse(array.slice()); // Используйте lodash для обращения массива
console.log('Reversed Array:', reversedArray);
Экспорт и импорт пользовательских модулей (модули CommonJS)
1. Экспорт модулей:Используйтеmodule.exportsилиexportsдля экспорта функций, объектов или переменных.
2. Импорт модулей:Используйтеrequire()для импорта модулей.
Импорт модулей
В Node.js импорт модуля очень прост. Ниже мы создаём файлmain.jsи импортируем модуль hello. Код выглядит так:
Код файла main.js:
hello.world();
В приведённом выше примере кодrequire('./hello')импортирует файлhello.jsв текущем каталоге (./— это текущий каталог, а расширение по умолчанию для файлов Node.js —.js)。
Node.js предоставляет два объекта: exports и require. exports — это публичный интерфейс модуля, а require используется для получения интерфейса модуля извне, то есть объекта exports подключаемого модуля.
Далее создадим файл hello.js. Код выглядит так:
Код файла hello.js:
console.log('Hello World');
}
В приведённом выше примере hello.js использует объект exports, чтобы сделать world интерфейсом доступа к модулю. В main.js, загрузив этот модуль с помощьюrequire('./hello'), мы можем затем напрямую обращаться к функциям-членам объектаexportsв hello.js.
Иногда мы просто хотим инкапсулировать объект в модуле. Формат выглядит так:
module.exports = function() {
// ...
}
Например:
Код файла hello.js:
function Hello() {
var name;
this.setName = function(thyName) {
name = thyName;
};
this.sayHello = function() {
console.log('Hello ' + name);
};
};
module.exports = Hello;
Таким образом, вы можете напрямую получить этот объект:
Код файла main.js:
var Hello = require('./hello');
hello = new Hello();
hello.setName('BYVoid');
hello.sayHello();
Единственное изменение в интерфейсе модуля — использованиеmodule.exports = Helloвместоexports.world = function(){}. Когда модуль импортируется извне, его объект интерфейса — сам экспортируемый объект Hello, а не исходный exports.
Разница между module.exports и exports:
module.exportsявляется реальной ссылкой на экспортируемый объект.exportsДаmodule.exports— это сокращение для него. Нельзя напрямую присваиватьexports = ..., иначе ссылка будет разорвана.
Пользовательский экспорт и импорт модулей (ES Modules)
ES-модули используютimportиexport, что является современной спецификацией модулей JavaScript.
- ES-модули используют
importиexportключевое слово, а расширение файла должно быть установлено в.mjs, или вpackage.jsondeclare"type": "module"。 - ES-модули поддерживают статический импорт (
import ... from ...) и динамические импорты (import())。
Пример
export function greet(name) {
return `Привет, ${name}!`;
}
// main.mjs
import { greet } from './myModule.mjs';
console.log(greet('Bob')); // Вывод: Hello, Bob!
Разрешение путей для загрузки модулей
1. Основные модули:Такие как http и fs, которые включены при установке Node.js и могут загружаться напрямую.
const http = require('http');2. Локальные файловые модули:Загружаются с использованием относительных или абсолютных путей; нужно указать./или/。
const myModule = require('./myModule');3. Сторонние модули:Расположены вnode_modulesкаталоге; вам нужно только ввести имя модуля, чтобы загрузить их.
const express = require('express');
Кэширование модулей
Механизм кэширования модулей:Node.js кэширует загруженные модули для повышения производительности. Когда require() вызывается снова для того же модуля, кэшированный модуль возвращается напрямую вместо повторной загрузки.
Обновление кэша:Чтобы перезагрузить модуль, можно удалить кэш:
delete require.cache[require.resolve('./myModule')];
Циклические зависимости
Когда два или более модуля импортируют друг друга, это называется циклической зависимостью. Node.js может обрабатывать простые циклические зависимости, но это может привести к тому, что объекты, экспортируемые некоторыми модулями, окажутся не полностью инициализированными.
Пример
const b = require('./b');
console.log('a.js:', b.message);
module.exports = { message: 'Hello from a' };
// b.js
const a = require('./a');
console.log('b.js:', a.message);
module.exports = { message: 'Hello from b' };
// main.js
require('./a');
Вывод показывает, что когда b.js импортирует a.js, поскольку a.js не был полностью выполнен, a.message имеет значение undefined.
Обёртка модулей и область видимости
Node.js оборачивает каждый модуль в функцию, предоставляя каждому модулю независимую область видимости. Пример:
// Node.js 内部将模块包装为类似这样的函数:
(function(exports, require, module, __filename, __dirname) {
// 模块代码在这里
});Это означает, что переменные, определённые в модуле, не будут загрязнять глобальную область видимости.
Где размещаются серверные модули?
Возможно, вы заметили, что мы уже использовали модули в нашем коде. Вот так:
var http = require("http");
...
http.createServer(...);
В Node.js есть модуль под названиемhttpМы подключаем его в коде с помощью require и присваиваем возвращаемое значение локальной переменной.
Это превращает нашу локальную переменную в объект, который содержит все публичные методы, предоставляемые модулем http.
Стратегия поиска файлов в методе require Node.js выглядит следующим образом:
Поскольку в Node.js существует 4 типа модулей (нативные модули и 3 типа файловых модулей), хотя метод require чрезвычайно прост, внутренняя загрузка довольно сложна, и приоритеты загрузки различаются. Как показано на следующей диаграмме:

Загрузка из кэша файловых модулей
Хотя нативные модули и файловые модули имеют разные приоритеты, оба сначала загружают существующие модули из кэша файловых модулей.
Загрузка из нативных модулей
Нативные модули имеют приоритет, уступающий только кэшу файловых модулей. После того как метод require разрешает имя файла, он сначала проверяет, находится ли модуль в списке нативных модулей. На примере модуля http: даже если в каталоге существует файл http/http.js/http.node/http.json, require("http") не будет загружаться из этих файлов, а из нативного модуля.
Нативные модули также имеют область кэша, из которой также сначала выполняется загрузка. Если кэш ещё не загружен, вызывается метод загрузки нативного модуля, чтобы загрузить и выполнить его.
Загрузка из файлов
Когда файловый модуль отсутствует в кэше и не является встроенным модулем, Node.js разбирает параметры, переданные методу require, и загружает фактический файл из файловой системы. Детали обёртывания и компиляции в процессе загрузки были представлены в предыдущем разделе. Здесь мы подробно опишем процесс поиска файловых модулей, среди которых также есть некоторые детали, которые стоит знать.
Метод require принимает следующие типы передачи параметров:
- http, fs, path и т. д. — встроенные модули.
- ./mod или ../mod — файловые модули с относительными путями.
- /pathtomodule/mod — файловые модули с абсолютными путями.
- mod — файловый модуль, не являющийся встроенным модулем.
Порядок выполнения инструкции require(X), выполняемой по пути Y:
1. 如果 X 是内置模块 a. 返回内置模块 b. 停止执行 2. 如果 X 以 '/' 开头 a. 设置 Y 为文件根路径 3. 如果 X 以 './' или '/' or '../' 开头 a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 4. LOAD_NODE_MODULES(X, dirname(Y)) 5. 抛出异常 "not found" LOAD_AS_FILE(X) 1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。 2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。 3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。 4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。 LOAD_INDEX(X) 1. 如果 X/index.js 是一个文件, 将 X/index.js 作为 JavaScript 文本载入并停止执行。 2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。 3. 如果 X/index.node 是一个文件, 将 X/index.node 作为二进制插件载入并停止执行。 LOAD_AS_DIRECTORY(X) 1. 如果 X/package.json 是一个文件, a. 解析 X/package.json, 并查找 "main" 字段。 b. let M = X + (json main 字段) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X) LOAD_NODE_MODULES(X, START) 1. let DIRS=NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. DIRS = DIRS + DIR d. let I = I - 1 5. return DIRS
Другие расширенияИспользование exports и module.exports
Если вы хотите предоставить свойства или методы наружу, используйтеexportsВот и всё. Чтобы предоставить объект (похожий на класс, содержащий множество свойств и методов), используйтеmodule.exports。