Skip to content

Latest commit

 

History

History
154 lines (110 loc) · 12.4 KB

File metadata and controls

154 lines (110 loc) · 12.4 KB

Модули в Node.js

Одной из ключевых возможностей Node.js, унаследованной от её серверной природы, является мощная модульная система. В отличие от веб-браузера, где загрузка десятков отдельных JavaScript-файлов по сети была бы медленной, Node.js работает в среде с быстрой файловой системой. Это позволяет разбивать код на множество небольших, логически организованных файлов — модулей. Такой подход делает код более структурированным, удобным для сопровождения и повторного использования.

Концепция модуля: Приватность и обмен кодом

Каждый файл с кодом JavaScript (с расширением .js, .mjs или .cjs) в Node.js считается самостоятельным модулем. Самое важное, что нужно понять — все переменные, функции, константы и классы, объявленные в модуле, являются приватными.

Представьте, что каждый файл обёрнут в собственную, невидимую функцию, которая изолирует его код от глобальной области видимости. Это предотвращает конфликты имён между разными частями вашей программы.

Чтобы поделиться частью своей функциональности с другими модулями, модуль должен её экспортировать. Соответственно, чтобы использовать код из другого модуля, ваш модуль должен его импортировать.

Экспорт: Делаем код доступным для других

Node.js автоматически создает для каждого модуля специальный объект module. У этого объекта есть свойство exports (module.exports). Именно этот объект и возвращается другим модулям, когда они импортируют ваш модуль с помощью require(). Для удобства также предоставляется прямая ссылка на этот объект — переменная exports.

Способ 1: Постепенное добавление в exports

Вы можете добавлять новые свойства к объекту exports, чтобы экспортировать функции, объекты или значения по одному.

Пример: Модуль stats.js с утилитами для статистики

// Внутри файла stats.js

// Эти функции приватны для модуля, если не экспортировать их
const sum = (x, y) => x + y;
const square = (x) => x * x;

// Экспортируем публичные методы, добавляя их в объект exports
exports.mean = (data) => data.reduce(sum) / data.length;

// Обратите внимание: внутри мы можем использовать exports.mean
exports.stddev = function(d) {
  let m = exports.mean(d);
  return Math.sqrt(d.map(x => x - m).map(square).reduce(sum) / (d.length - 1));
};

В этом примере модуль предоставляет наружу объект с двумя методами: mean и stddev. Внутренние функции sum и square остаются скрытыми от внешнего мира.

Способ 2: Единый экспорт через module.exports

Часто модуль предназначен для экспорта всего одного значения: например, одного класса или функции. В этом случае принято полностью перезаписывать объект module.exports.

Важное отличие: exports — это просто ссылка на изначальный module.exports. Если вы присвоите новой переменной exports, вы разорвете эту связь. Поэтому для одиночного экспорта всегда используйте module.exports.

Пример: Экспорт одного класса

// Внутри файла bitset.js
class BitSet {
  // реализация класса
}

// Мы хотим, чтобы при импорте модуль возвращал этот класс, а не объект
module.exports = BitSet; // Теперь require('./bitset.js') вернет сам класс BitSet

Пример: Экспорт объекта в конце файла Альтернативный и очень чистый подход — сначала объявить всё, а затем одним махом экспортировать нужные части.

// Внутри файла stats-v2.js

// 1. Сначала объявляем всё (и публичное, и приватное)
const sum = (x, y) => x + y;
const square = (x) => x * x;
const mean = (data) => data.reduce(sum) / data.length;
const stddev = (d) => {
  let m = mean(d);
  return Math.sqrt(d.map(x => x - m).map(square).reduce(sum) / (d.length - 1));
};

// 2. Затем создаем публичный API, экспортируя объект
module.exports = { mean, stddev }; // Используем сокращенную запись свойства из ES6

Импорт: Использование кода из других модулей

Для импорта модулей в Node.js используется глобальная функция require().

Импорт встроенных и сторонних модулей

Если вы импортируете модуль, встроенный в Node.js (например, fs для работы с файлами) или установленный через npm (например, express), вы просто указываете его имя.

const fs = require('fs'); // Встроенный модуль 'fs'
const express = require('express'); // Сторонний модуль, установленный via npm
const http = require('http'); // Встроенный модуль 'http'

Node.js знает, где искать такие модули.

Импорт ваших собственных модулей

Чтобы импортировать ваш собственный файл, нужно передать в require() путь к этому файлу, относительный к текущему файлу.

// Импорт модуля из той же папки
const stats = require('./stats.js'); // .js можно опустить: require('./stats')

// Импорт модуля из дочерней папки 'utils'
const BitSet = require('./utils/bitset.js');

// Импорт модуля из родительской папки
const config = require('../config.js');

Обратите внимание на префиксы ./ и ../. Они критически важны, так как говорят Node.js, что это не встроенный модуль, а локальный файл. Без них Node.js будет искать модуль с именем stats в стандартных папках и среди установленных пакетов.

Как работать с тем, что вернул require()?

То, что вы получите в переменную (stats, BitSet и т.д.), зависит от того, что модуль экспортировал через module.exports.

  1. Если модуль экспортирует объект с несколькими свойствами (как stats.js): Вы можете сохранить весь объект и обращаться к его свойствам.

    const stats = require('./stats.js');
    let average = stats.mean([1, 2, 3, 4, 5]);
    let deviation = stats.stddev([1, 2, 3, 4, 5]);

    Это создает четкое пространство имен (stats), что делает код понятнее.

    Или вы можете использовать деструктуризацию, чтобы сразу извлечь нужные функции в отдельные переменные.

    const { mean, stddev } = require('./stats.js');
    let average = mean([1, 2, 3, 4, 5]);
    let deviation = stddev([1, 2, 3, 4, 5]);

    Этот код более лаконичен, но может быть менее ясен его контекст, если импортируется много функций из разных модулей.

  2. Если модуль экспортирует одно значение (как bitset.js): Вы получаете это значение напрямую.

    const BitSet = require('./utils/bitset.js'); // BitSet теперь это класс
    const myBitSet = new BitSet();

Эволюция модулей: Замечание о современных стандартах

Описанный выше синтаксис с require() и module.exports является стандартом для Node.js и известен как CommonJS (CJS).

Однако современный JavaScript (ECMAScript) имеет собственную, нативную систему модулей, которая использует ключевые слова import и export. Она часто называется ES Modules (ESM).

  • Вместо require() используется import.
  • Вместо module.exports используется export.

Со временем ES Modules становятся все более популярными и поддерживаются в Node.js. Многие современные проекты, особенно те, которые используют сборщики (like Webpack, Vite) или предназначены и для браузера, и для сервера, предпочитают синтаксис ES Modules.

Тем не менее, огромное количество существующих проектов и библиотек в экосистеме Node.js до сих пор используют CommonJS, поэтому его понимание абсолютно необходимо для любого разработчика.

Итог и лучшие практики

  1. Изоляция: Каждый файл — это отдельная вселенная. Ничто из него не «утекает» наружу без явного экспорта.
  2. Экспорт: Используйте exports.myFunction для экспорта нескольких значений. Используйте module.exports = myValue для экспорта одного значения (класса, функции, объекта).
  3. Импорт: Используйте const myModule = require('./path/to/module') для импорта.
  4. Пути: Всегда используйте ./ или ../ для импорта своих файлов.
  5. Ясность: Выбирайте способ импорта (весь объект или деструктуризацию) в зависимости от контекста и того, что делает код более читаемым.

Понимание модулей — это фундаментальный шаг к написанию чистой, хорошо организованной и масштабируемой кодовой базы на Node.js. Практикуйтесь в создании и импорте своих модулей, чтобы почувствовать эту мощную концепцию.