NodeJS Back-end Инженер

NodeJS Back-end Инженер

Роадмап навыков для прокачки

Основы и использование модульной системы Node.js

Node.jsModule systemexports, globals

Основная идея

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

Ключевые аспекты

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

Плюсы модульной системы

  • Инкапсуляция кода и данных
  • Повторное использование модулей
  • Чёткие зависимости между частями приложения
  • Упрощение тестирования

Минусы

  • Синхронная загрузка может блокировать выполнение
  • CommonJS не поддерживает tree-shaking
  • Циклические зависимости могут вызвать проблемы

Частые ошибки на собеседованиях

  • Путают exports и module.exports — присваивание exports = разрывает связь с module.exports
  • Забывают, что require кэширует модули
  • Не понимают разницу между CommonJS и ES Modules

Введение и проблематика

В раннем JavaScript весь код выполнялся в глобальной области видимости. Это приводило к конфликтам имён, сложности поддержки и невозможности повторного использования кода.

Node.js решил эту проблему, внедрив модульную систему CommonJS, где каждый файл — это отдельный модуль со своей областью видимости.

Модульная система Node.js появилась до стандартизации ES Modules и остаётся основной для серверного JavaScript.


Базовая теория

Что такое модуль?

Модуль в Node.js — это отдельный файл с JavaScript-кодом. Каждый модуль имеет:

  • Собственную область видимости (переменные не попадают в global)
  • Объект module с метаинформацией
  • Объект exports для экспорта функциональности
  • Функцию require для импорта других модулей

Структура модуля

Node.js оборачивает каждый модуль в функцию:

js
(function(exports, require, module, __filename, __dirname) {
  // Ваш код модуля здесь
});

Это объясняет, почему переменные модуля изолированы и откуда берутся __dirname и __filename.


Практические примеры

Экспорт и импорт модулей

js
// math.js
function add(a, b) {
  return a + b;
}
 
function subtract(a, b) {
  return a - b;
}
 
// Экспортируем функции
module.exports.add = add;
module.exports.subtract = subtract;
 
// Или короче через exports
exports.multiply = (a, b) => a * b;
js
// app.js
const math = require('./math');
 
console.log(math.add(2, 3));      // 5
console.log(math.subtract(5, 2)); // 3
console.log(math.multiply(4, 2)); // 8

Структура проекта с модулями

    • app.js
      • math.js
      • string-utils.js
    • package.json
  • Алгоритм поиска модулей

    Встроенные модули

    Node.js сначала проверяет, является ли имя встроенным модулем (fs, path, http).

    Относительные пути

    Если путь начинается с ./ или ../, Node.js ищет файл относительно текущего модуля.

    Папка node_modules

    Для абсолютных имён Node.js ищет в node_modules текущей папки, затем родительской, и так до корня.


    Пограничные кейсы

    ⚠️

    Разница между exports и module.exports

    js
    // ❌ Неправильно — разрывает связь
    exports = { name: 'Test' };
     
    // ✅ Правильно — присваиваем свойства
    exports.name = 'Test';
     
    // ✅ Правильно — заменяем весь объект
    module.exports = { name: 'Test' };
    🚫

    Циклические зависимости

    js
    // a.js
    const b = require('./b');
    console.log('в a.js, b.done =', b.done);
    exports.done = true;
     
    // b.js
    const a = require('./a');
    console.log('в b.js, a.done =', a.done);
    exports.done = true;
     
    // При запуске a.js:
    // в b.js, a.done = undefined (a ещё не завершён!)
    // в a.js, b.done = true

    Кэширование модулей

    Node.js кэширует загруженные модули. Повторный require() возвращает тот же объект:

    js
    // counter.js
    let count = 0;
    module.exports = {
      increment: () => ++count,
      getCount: () => count
    };
     
    // app.js
    const counter1 = require('./counter');
    const counter2 = require('./counter');
     
    counter1.increment();
    console.log(counter2.getCount()); // 1 — тот же экземпляр!
     
    // Проверка кэша
    console.log(counter1 === counter2); // true

    Плюсы и минусы

    АспектCommonJSES Modules
    Синтаксисrequire/exportsimport/export
    ЗагрузкаСинхроннаяАсинхронная
    Tree-shaking❌ Нет✅ Да
    Динамический импорт✅ Легкоimport()
    ПоддержкаNode.jsБраузеры + Node.js

    Вопросы интервьюера

    Q: Чем отличается exports от module.exports?

    exports — это ссылка на module.exports. Присваивание exports = {} разрывает эту связь, и экспорт перестаёт работать.

    Q: Что происходит при циклических зависимостях?

    Node.js возвращает частично заполненный объект exports модуля, который ещё не завершил выполнение.

    Q: Как сбросить кэш модуля?

    delete require.cache[require.resolve('./module')] — но это антипаттерн, используется только в тестах.

    Q: Когда использовать CommonJS, а когда ES Modules?

    Для новых проектов рекомендуется ES Modules. CommonJS используется для совместимости со старым кодом.


    Источники