module.exports против exports в Node.js: что выбрать?
У меня есть следующая проблема, с которой я столкнулся в модуле Node.js:
Я нашел следующий код в одном из модулей:
module.exports = exports = nano = function database_module(cfg) {...}
Мне было бы интересно узнать, в чем разница между module.exports
и exports
, и почему оба используются в данном случае.
5 ответ(ов)
Установка module.exports
позволяет вызывать функцию database_module
как обычную функцию после её импорта с помощью require
. Просто установка exports
не позволяет экспортировать функцию, так как Node.js экспортирует объект, на который ссылается module.exports
. Следующий код не даст пользователю возможность вызвать функцию.
module.js
Следующий код не будет работать.
exports = nano = function database_module(cfg) {return;}
Следующий код будет работать, если установлен module.exports
.
module.exports = exports = nano = function database_module(cfg) {return;}
console
var func = require('./module.js');
// следующая строка **будет работать** с module.exports
func();
В общем, Node.js не экспортирует объект, на который ссылается exports
в текущий момент, а экспортирует свойства объекта, на который exports
ссылается изначально. Однако Node.js действительно экспортирует объект, на который ссылается module.exports
, что позволяет вызывать его как функцию.
Второстепенная причина
Установив как module.exports
, так и exports
, мы гарантируем, что exports
не ссылается на предыдущий экспортированный объект. Устанавливая оба, мы используем exports
как сокращение и избегаем потенциальных ошибок в будущем.
Использование exports.prop = true
вместо module.exports.prop = true
экономит символы и избегает путаницы.
Ответ на ваш вопрос заключается в том, что происходит, когда модуль загружается с помощью оператора require
. Предположим, что модуль загружается в первый раз.
Пример:
var x = require('file1.js');
Содержимое файла file1.js
:
module.exports = '123';
Когда выполняется вышеуказанная инструкция, создается объект Module
. Его конструктор выглядит так:
function Module(id, parent) {
this.id = id;
this.exports = {};
this.parent = parent;
if (parent && parent.children) {
parent.children.push(this);
}
this.filename = null;
this.loaded = false;
this.children = [];
}
Как вы можете видеть, каждый объект модуля имеет свойство exports
. Именно это значение будет возвращено в результате выполнения require
.
Следующий шаг в процессе загрузки модуля — обернуть содержимое file1.js
в анонимную функцию, как показано ниже:
(function (exports, require, module, __filename, __dirname) {
// содержимое файла file1.js
module.exports = '123';
});
Эта анонимная функция вызывается следующим образом, при этом module
ссылается на ранее созданный объект Module
.
(function (exports, require, module, __filename, __dirname) {
// содержимое файла file1.js
module.exports = '123';
}) (module.exports, require, module, "path_to_file1.js", "директория файла file1.js");
Как видно из примера ниже, параметр exports
ссылается на module.exports
. Это своего рода удобство для разработчиков модулей.
Тем не менее, с этим удобством нужно обращаться осторожно. В случае, если вы хотите присвоить новое значение exports
, убедитесь, что делаете это так:
exports = module.exports = {};
Если же сделать это следующим образом (неправильный способ):
exports = {};
То module.exports
по-прежнему будет указывать на объект, созданный в процессе инициализации модуля. В результате добавление чего-либо в родственный объект exports
не повлияет на объект module.exports
, и ничего не будет экспортировано или возвращено при вызове require
.
Изначально module.exports = exports
, и функция require
возвращает объект, на который ссылается module.exports
.
Если мы добавим свойство в объект, например, exports.a = 1
, то module.exports
и exports
по-прежнему ссылаются на один и тот же объект. Таким образом, если мы вызовем require
и присвоим модуль переменной, то у этой переменной будет свойство a
со значением 1
.
Но если мы переопределим один из них, например, exports = function() {}
, то теперь они разные: exports
ссылается на новый объект, а module.exports
— на исходный объект. Поэтому если мы вызовем файл, он не вернет новый объект, так как module.exports
не ссылается на новый объект.
Что касается меня, я предпочитаю продолжать добавлять новые свойства или переопределять оба объекта в новый. Просто переопределять один из них — не совсем корректно. И не забывайте, что module.exports
— это реальный "босс".
exports
и module.exports
— это одно и то же, если вы не переназначаете exports
в вашем модуле.
Самый простой способ это понять — представить, что эта строка неявно добавляется в начало каждого модуля:
var exports = module.exports = {};
Если в вашем модуле вы переназначаете exports
, то вы переназначаете его, и он больше не будет равен module.exports
. Поэтому, если вы хотите экспортировать функцию, вам нужно делать так:
module.exports = function() { ... }
Если же вы просто присвоите свою function() { ... }
exports
, то вы переназначите exports
, и он больше не будет указывать на module.exports
.
Если вам не хочется каждый раз ссылаться на вашу функцию через module.exports
, вы можете сделать так:
module.exports = exports = function() { ... }
Обратите внимание, что module.exports
находится слева.
Прикрепление свойств к exports
не то же самое, поскольку вы не переназначаете его. Поэтому это работает:
exports.foo = function() { ... }
В JavaScript объекты передаются по копии ссылки
Это тонкое различие связано с тем, как в JavaScript передаются объекты по ссылке.
exports
и module.exports
оба указывают на один и тот же объект. exports
— это переменная, а module.exports
— это свойство объекта модуля.
Допустим, я пишу что-то вроде этого:
exports = {a:1};
module.exports = {b:12};
Теперь exports
и module.exports
указывают на разные объекты. Изменение exports
больше не изменяет module.exports
.
Когда функция импорта проверяет module.exports
, она получает {b:12}
.
Функция map для объектов (вместо массивов)
Как получить полный объект в console.log() Node.js, а не '[Object]'?
Самый быстрый способ скопировать файл в Node.js
Обнаружена ошибка: Невозможное нарушение: Неверный тип элемента: ожидался строковый тип (для встроенных компонентов) или класс/функция, но получен объект
Как сделать паузу на определенное время в Node.js (JavaScript)?