Многие новички, иногда, приходят в конфуз, связанный с тем, что на серверной стороне в Node.js используются включение отдельных скриптов и модулей через ключевое слово require, хотя, если глянуть в стандарт клиентского языка JavaScript, то там он не используется.
Модульная система Node.js
[note]Node.js обрабатывает каждый файл JavaScript как отдельный модуль. Но этот способ подключения не используется на клиентской стороне в целях безопасности[/note]
Например, если у вас есть файл, содержащий некоторый код, и этот файл называется xyz.js, то этот файл рассматривается как модуль в Node, и вы можете сказать, что создали модуль с именем xyz.
Давайте возьмем пример, чтобы понять это лучше.
У вас есть файл с именем circle.js, который состоит из логики для вычисления площади и окружности круга заданного радиуса, как показано ниже:
// constant const PI = 3.14; /** * Function to calculate area of a circle with given radius r * @param {number} r * @returns {number} area of circle */ const calculateArea = r => PI * r * r; /** * Function to calculate circumference of a circle with given radius r * @param {number} r * @returns {number} circumference of circle */ const calculateCircumference = r => 2 * PI * r;
Вы можете назвать файл circle.js модулем с именем circle.
Вам может быть интересно, почему нужно иметь несколько модулей? Вы могли бы просто написать весь код в одном модуле. Чт�� ж, очень важно написать модульный код. Под модульным я хочу сказать, что ваш код должен быть независимым и должен быть слабо связанным. Представьте, что есть большое приложение, и весь ваш код написан в одном месте, в одном файле. Слишком грязно, верно?
Как работает код, написанный внутри модуля?
Перед выполнением кода, написанного внутри модуля, Node берет весь код и заключает его в оболочку функции. Синтаксис этой функции-обёртки:
Функциональная оболочка для модуля circle будет выглядеть так, как показано ниже:
(function(exports, require, module, __filename, __dirname) { // all the code written inside the 'circle' module will breathe inside this function wrapper // constant const PI = 3.14; /** * Function to calculate area of a circle with given radius r * @param {number} r * @returns {number} area of circle */ const calculateArea = r => PI * r * r; /** * Function to calculate circumference of a circle with given radius r * @param {number} r * @returns {number} circumference of circle */ const calculateCircumference = r => 2 * PI * r; });
Вы можете видеть, что на корневом уровне есть функция-обертка, охватывающая весь код, написанный внутри модуля circle.
[info]Весь код, написанный внутри модуля, является частным для модуля, если явно не указано, как экспортированное через ключевое слово export. [/info]
Это самое значительное преимущество наличия модулей в Node.js. Даже если вы определяете глобальную переменную в модуле, используя ключевые слова var, let или const, переменные ограничиваются локальной областью модуля, а не глобально. Это происходит потому, что каждый модуль имеет свою собственную оболочку функции, а код, написанный внутри одной функции, является локальным для этой функции и недоступен вне этой функции.
Представьте, что есть два модуля — A и B. Код, написанный внутри модуля A, заключен в оболочку функции, соответствующую модулю A. Подобное происходит с кодом, написанным внутри модуля B. Потому что код, относящийся к обоим модулям заключен в различные функции, эти функции не смогут получить доступ к коду друг друга. (Помните, что каждая функция в JavaScript имеет свою локальную область видимости?) По этой причине модуль A не может получить доступ к коду, написанному внутри модуля B, и наоборот.
[info] Пять параметров — export, require, module, __filename, __dirname доступны внутри каждого модуля в Node. [/info]
Хотя эти параметры являются глобальными для кода в модуле, они являются локальными для модуля (из-за функции-оболочки, как описано выше). Эти параметры предоставляют ценную информацию, связанную с модулем.
Давайте вернемся к модулю circle, который вы рассматривали ранее. В этом модуле определены три конструкции: константа-переменная PI, функция с именем CalculaArea и другая функция с именем CalculateCircumference. Важно помнить, что все эти конструкции по умолчанию являются частными для модуля circle. Это означает, что вы не можете использовать эти конструкции в любом другом модуле, если это не указано явно.
Итак, возникает вопрос: как указать что-то в модуле, который может быть использован другим модулем? Это когда модуль и требуются параметры функции оболочки являются полезными. Давайте обсудим эти два параметра в этой статье.
Параметр module
Параметр module (скорее ключевое слово в модуле в Node) относится к объекту, представляющему текущий модуль. exports— это ключевое слово объекта модуля, соответствующее значение которого является объектом.
Значением по умолчанию для модуля module.exports является {} (пустой объект). Вы можете проверить это, зарегистрировав значение ключевого слова module внутри любого модуля. Давайте проверим, каково значение параметра module внутри модуля circle
// constant const PI = 3.14; /** * Function to calculate area of a circle with given radius r * @param {number} r * @returns {number} area of circle */ const calculateArea = r => PI * r * r; /** * Function to calculate circumference of a circle with given radius r * @param {number} r * @returns {number} circumference of circle */ const calculateCircumference = r => 2 * PI * r; // logging the contents of module object console.log(module);
Обратите внимание, в коде есть строчка вывода в консоль console.log (module). Переде тем, как вывести модуль регистрирует объект module, у которого есть ключ с именем exports, и значение, соответствующее этому ключу, равно {} (пустой объект).
Теперь, что делает объект module.exports? Ну, это используется для определения вещей, которые могут быть экспортированы модулем.
[note]Все, что экспортируется из модуля, может, в свою очередь, быть доступным для других модулей. [/note]
Экспортировать что-то довольно легко. Вам просто нужно добавить его в объект module.exports. Есть три способа добавить что-то к объекту module.exports для экспорта. Давайте обсудим эти методы один за другим.
Экспорт свойств из модуля
Для экспорта вы сначала определяете конструкции, а затем используете несколько операторов module.exports, где каждая инструкция используется для экспорта чего-либо из модуля.
Давайте посмотрим на этот способ в действии и посмотрим, как можно экспортировать две функции, определенные в модуле circle.
// constant const PI = 3.14; /** * Function to calculate area of a circle with given radius r * @param {number} r * @returns {number} area of circle */ const calculateArea = r => PI * r * r; /** * Function to calculate circumference of a circle with given radius r * @param {number} r * @returns {number} circumference of circle */ const calculateCircumference = r => 2 * PI * r; // exporting stuff by adding to module.exports object module.exports.calculateArea = calculateArea; // exporting function named calculateArea module.exports.calculateCircumference = calculateCircumference; // exporting function named calculateCircumference
Как я уже говорил ранее, module — это объект, имеющий ключ с именем exports, и этот ключ (module.exports), в свою очередь, состоит из другого объекта. Теперь, если вы заметили приведенный выше код, все, что вы делаете, это добавляете новые свойства (пары ключ-значение) в объект module.exports.
Первое свойство имеет ключ calculateArea (определен в строке 19), а значение, записанное в правой части оператора присваивания, является функцией, определенной с именем calculateArea (в строке 9).
Второе свойство (определено в строке 20) имеет ключ calculateCircumference, а значением является функция, определенная с именем calculateCircumference (в строке 16).
Таким образом, вы присвоили два свойства (пары ключ-значение) объекту module.exports.
Кроме того, давайте не будем забывать, что вы использовали здесь точечную запись. В качестве альтернативы вы можете использовать нотацию в квадратных скобках для назначения свойств объекту module.exports и добавить функции — возложить на calculateArea и calculateCircumference, указав ключи, следующие за нотацией в скобках. Таким образом, вы можете написать следующие две строки, чтобы добавить свойства к объекту module.exports, используя скобочные обозначения, заменив последние две строки в приведенном выше коде:
... // exporting stuff by adding to module.exports object using the bracket notation module.exports['calculateArea'] = calculateArea; module.exports['calculateCircumference'] = calculateCircumference; ...
Давайте теперь попробуем записать значение объекта module.exports после добавления свойств. Обратите внимание, что следующий оператор вывода значения объекта module добавляется в конец кода в приведенном ниже файле:
// constant const PI = 3.14; /** * Function to calculate area of a circle with given radius r * @param {number} r * @returns {number} area of circle */ const calculateArea = r => PI * r * r; /** * Function to calculate circumference of a circle with given radius r * @param {number} r * @returns {number} circumference of circle */ const calculateCircumference = r => 2 * PI * r; // exporting stuff by adding to module.exports object module.exports.calculateArea = calculateArea; // exporting function named calculateArea module.exports.calculateCircumference = calculateCircumference; // exporting function named calculateCircumference // logging the contents of module.exports object after adding properties to it console.log(module.exports);
Давайте проверим вывод этого кода и посмотрим, все ли работает нормально. Для этого сохраните свой код и выполните следующую команду в своем терминале:
$ node circle
Это выведет
{
calculateArea: [Function: calculateArea],
calculateCircumference: [Function: calculateCircumference]
}
Конструкции — calculateArea и calculateCircumference, добавленные в module.exports, регистрируются в объекте module.exports. Таким образом, вы успешно добавили два свойства в объект module.exports, чтобы можно было экспортировать функции — calculateArea и calculateCircumference в другой модуль.
В этом методе вы сначала определили все конструкции, а затем использовали несколько операторов module.exports, где каждый оператор используется для добавления свойства к объекту module.exports.
Можно было не использовать несколько выводов, а ограничиться одним выводом объектас нужными свойствами:
... // using object literal notation to export stuff by adding all at once to module.exports object module.exports = { calculateArea : calculateArea, calculateCircumference : calculateCircumference } ...
Еще один способ вывода — это использовать экспортирование на этапе вычислений нужных свойств и сразу же применить
// constant const PI = 3.14; /** * Function to calculate area of a circle with given radius r * @param {number} r * @returns {number} area of circle */ // adding function definition as the value corresponding to the key calculateArea while defining the function module.exports.calculateArea = r => PI * r * r; /** * Function to calculate circumference of a circle with given radius r * @param {number} r * @returns {number} circumference of circle */ // adding function definition as the value corresponding to the key calculateCircumference while defining the function module.exports.calculateCircumference = r => 2 * PI * r; // no need to write any module.exports statement later // logging the contents of module.exports object after adding properties to it console.log(module.exports);
Функция require
Ключевое слово require относится к функции, которая используется для импорта всех конструкций, экспортированных с использованием объекта module.exports из другого модуля.
Если у вас есть модуль x, в который вы экспортируете некоторые конструкции, используя объект module.exports, и вы хотите импортировать эти экспортированные конструкции в модуль y, вам потребуется импортировать модуль x в модуле y с помощью функции require. Значение, возвращаемое функцией require в модуле y, равно объекту module.exports в модуле x.
Давайте разберемся в этом на примере, который мы обсуждали ранее. У вас уже есть модуль circle, из которого вы экспортируете функции calculateArea и calculateCircumference. Теперь давайте посмотрим, как вы можете использовать функцию require для импорта экспортированного материала в другой модуль.