Одна из концепций, которые нужно понять в Node.js, — это использование require, module.exports и exports. Среда выполнения Node расширяет Javascript файловым вводом-выводом и возможностью экспортировать файлы и использовать их функциональные возможности в других файлах, благодаря чему достигается модульность.

Начиная с ES6, ядро ​​Javascript выполняет то же самое, но с немного другой реализацией и ключевыми словами import и export. В будущем все может измениться, но пока Node использует собственную реализацию.

Внутренняя работа

Начнем с простого примера: модуль с функцией вычисления длины окружности и еще один, использующий эту функцию через require. Запустите пример с node index, чтобы увидеть вывод консоли.

Что здесь происходит, шаг за шагом?

  • Все файлы, которые выполняются nodejs, сначала получают объект module, который всегда будет вам доступен. Он содержит информацию для id и filename.
  • Свойство exports этого объекта инициируется ссылкой на пустой объект {}
  • Будучи простым объектом javascript, вы можете назначать его свойства, как мы это делаем в первой строке: module.exports.prop = ....
  • Функция require() используется для построения иерархии между parent и children, которая также регистрируется в объекте module.
  • Функция require('./requiredfile') возвращает ссылку на тот же объект, что и свойство module.exports требуемого файла.

Вы можете думать, что require наивно определяется следующим образом:

const require = path => {
  // ...
  return module.exports;
};

Мы можем провести небольшой эксперимент, чтобы продемонстрировать, что require() и module.exports на самом деле указывают на один и тот же объект.

Как и прежде, мы создаем ссылку с именем circle в нашем индексном файле, требуя модуль Circle. На этот раз мы добавляем функцию area к объекту circle и снова требуем модуль Circle. Оказывается, module.exports теперь также содержит новое добавленное свойство (поскольку оба относятся к одному и тому же объекту).

В сторону: как узел создает модуль?

Вы можете отлаживать свое приложение, чтобы просмотреть исходный код узла, который отвечает за создание модуля. Он находится в функции loader.js, в частности, в функции Module.wrap. Вот что происходит:

(function (exports, require, module, __filename, __dirname) {
  // YOUR CODE GOES HERE
});

Он в основном читает содержимое файла, помещает его в функцию-конструктор и выполняет эту функцию.

экспорт против module.exports

Node предоставляет ярлык exports. Это ссылка на тот же объект, на который указывает module.exports и результат require(). Ничего волшебного. Вы можете думать об этом как о явном:

let exports = module.exports;

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

Короче говоря, когда вы делаете exports = someValue, он больше не будет использовать общую ссылку с module.exports. Теперь, когда require() создает ссылку, она будет относиться к тому же объекту, что и module.exports, а не к значению, на которое ссылается exports.

Возможно, лучше всего это показано в другом примере кода:

Эмпирическое правило состоит в том, чтобы никогда не присваивать новое значение exports (но присваивание exports.someProperty вполне нормально).

использованная литература