Введение в модули JavaScript
Модули JavaScript помогают управлять крупными проектами, разбивая код на отдельные переиспользуемые части. Руководство по эффективному использованию модулей.
Модули JavaScript помогают управлять крупными проектами, позволяя разбивать код на отдельные переиспользуемые части. В этом руководстве подробно рассматривается, как эффективно использовать модули JavaScript для улучшения навыков программирования и организации проектов.
На этой странице объясняется, что такое модуль, рассматривается синтаксис import/export, различия между именованными и экспортами по умолчанию, принцип работы области видимости модулей, старые системы модулей, с которыми вы ещё будете сталкиваться (CommonJS и AMD), а также приводится полный рабочий пример, который можно запустить.
Понимание модулей JavaScript
Модуль — это просто файл. Каждый файл получает собственную приватную область видимости: переменные и функции, объявленные внутри модуля, не видны за его пределами, если их явно не export-ировать. Чтобы использовать что-то из другого модуля, его нужно import-ировать. Это основа всех остальных концепций на этой странице.
Модули также привносят два автоматических поведения, о которых следует знать:
- Строгий режим всегда включён. Каждый модуль выполняется так, как будто в его начале стоит
'use strict', поэтому случайные глобальные переменные вызываютReferenceErrorвместо того, чтобы тихо создавать переменную. thisна верхнем уровне равенundefined, а не глобальному объекту. Это один из самых простых способов определить, выполняется ли код как модуль.
Далее рассматривается основной синтаксис и использование модулей.
Введение в синтаксис модулей
Модули используют операторы import и export для обмена кодом между файлами.
Пример:
// Exporting functions
export const add = (a, b) => a + b;
export function multiply(a, b) {
return a * b;
}
// Importing them in another file
import { add, multiply } from './mathFunctions.js';Оператор export позволяет сделать части модуля доступными для других файлов. Оператор import позволяет подключать эти части там, где они нужны.
Используйте чёткие, описательные имена для модулей и функций. Это делает код более читаемым и удобным в сопровождении.
Экспорт по умолчанию и именованный экспорт
Для экспорта различных частей кода можно использовать экспорт по умолчанию или именованный экспорт. Именованный экспорт предоставляет доступ к нескольким функциям по имени, а экспорт по умолчанию предоставляет доступ к одной функции без указания имени.
Пример:
// mathFunctions.js
export default function subtract(a, b) {
return a - b;
}
// Using the default export
import subtract from './mathFunctions.js';Ключевое слово default используется для экспорта одной функции или переменной. При импорте экспорта по умолчанию можно использовать любое имя для обращения к нему.
Именованный экспорт хорошо подходит для вспомогательных функций, а экспорт по умолчанию удобен для единственной основной функциональности файла, например React-компонента или класса.
Файл может сочетать один экспорт по умолчанию с произвольным количеством именованных экспортов. Их импортируют вместе: сначала экспорт по умолчанию, затем именованные в фигурных скобках:
// mathFunctions.js
export const add = (a, b) => a + b; // named export
export default function subtract(a, b) { // default export
return a - b;
}
// app.js
import subtract, { add } from './mathFunctions.js';Несколько правил, которые стоит запомнить:
- В модуле может быть только один экспорт по умолчанию, но сколько угодно именованных экспортов.
- Именованные импорты должны использовать точное экспортируемое имя (или переименовывать через
as:import { add as sum } from './mathFunctions.js'). - Импорт по умолчанию может использовать любое имя, поскольку экспорт по умолчанию не имеет фиксированного имени.
Переименование и повторный экспорт
Можно переименовывать при импорте или экспорте с помощью as, а также повторно экспортировать из другого модуля для построения единой точки входа (файл-«баррель»):
// utils.js
export { add as sum } from './mathFunctions.js';
export { default as subtract } from './mathFunctions.js';
// app.js
import { sum, subtract } from './utils.js';Использование модулей для чистого кода
Применение модулей упрощает работу с кодом, особенно по мере роста проектов.
Структура директорий
Правильная организация папок помогает поддерживать порядок в коде.
Пример:
/src
/components
/helpers
/models
/services
index.jsУправление зависимостями
Управление зависимостями означает обеспечение корректного взаимодействия файлов с кодом.
Пример:
// Webpack configuration for bundling modules
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{ test: /\.js$/, use: 'babel-loader' }
]
}
};Эта конфигурация указывает Webpack начать с index.js, собрать его вместе со всеми зависимостями в bundle.js и поместить результат в папку dist.
Примечание: конфигурационные файлы Webpack традиционно используют синтаксис CommonJS (module.exports), даже в проектах, активно использующих ES6.
Продвинутые техники работы с модулями
Использование продвинутых возможностей модулей делает код более эффективным и удобным в управлении.
Динамические импорты
Динамические импорты позволяют загружать код только по мере необходимости, что ускоряет работу приложения.
Пример:
// Dynamically importing a module
// Assuming a button element exists
button.onclick = () => {
import('./messageModule.js')
.then(module => {
module.showMessage('Dynamic Import Executed!');
});
};Этот код загружает модуль только при нажатии на кнопку, что позволяет сократить время начальной загрузки.
Используйте динамические импорты для частей приложения, которые не нужны сразу, например для дополнительных функций, доступных позже.
import() возвращает промис, поэтому его можно использовать с async/await:
async function loadFeature() {
const module = await import('./messageModule.js');
module.showMessage('Loaded on demand');
}Подробнее см. в разделе Динамические импорты.
Взаимодействие между модулями
Модули должны быть самодостаточными, но могут взаимодействовать через общие ресурсы.
Пример:
// stateManager.js
export let state = { count: 0 };
// counter.js
import { state } from './stateManager.js';
state.count++;Этот код показывает два модуля, совместно использующих объект состояния. Поскольку модули JavaScript кэшируются как синглтоны, оба файла ссылаются на один и тот же объект в памяти. Когда один модуль изменяет состояние, другой видит это изменение.
Примечание: в этом примере общий объект мутируется напрямую. Для продакшн-приложений рассмотрите использование библиотеки управления состоянием или реактивного подхода для предсказуемой обработки обновлений.
Понимание систем модулей и их использование сегодня
В современной веб-разработке понимание различных систем модулей крайне важно:
- CommonJS: используется преимущественно в Node.js для серверного кода.
- AMD (Asynchronous Module Definition): используется для асинхронной загрузки модулей, подходит для браузеров.
- ES6 Modules: стандарт в современной веб-разработке, поддерживающий как синхронную, так и асинхронную загрузку.
Примеры для каждой системы модулей
Для иллюстрации этих концепций в JavaScript приведём примеры кода для каждого типа модулей наряду с существующим примером ES6.
Пример CommonJS:
// mathFunctions.js
exports.add = function(a, b) {
return a + b;
};
// app.js
const math = require('./mathFunctions.js');
console.log(math.add(5, 3));Пояснение к CommonJS: в этом примере используется exports для предоставления функции add за пределами файла. Затем в другом файле используется require для подключения функции add. Эта система широко применяется в Node.js.
Пример AMD:
// mathFunctions.js
define([], function() {
return {
add: function(a, b) {
return a + b;
}
};
});
// app.js
require(['mathFunctions'], function(math) {
console.log(math.add(5, 3));
});Пояснение к AMD: в этом примере используется define для объявления модуля без зависимостей, возвращающего объект с функцией add. Затем require используется для асинхронной загрузки модуля. Это удобно для динамической загрузки модулей в браузере.
Пример ES6 Modules:
// mathFunctions.js
export const add = (a, b) => a + b;
// app.js
import { add } from './mathFunctions.js';
console.log(add(5, 3));Пояснение к ES6 Modules: здесь используется export для предоставления функции add и import для её использования в другом файле. Это современный стандарт работы с модулями в JavaScript, поддерживаемый большинством браузеров.
Включение ES6 Modules в Node.js
При использовании ES6 Modules в Node.js можно применять тот же синтаксис import/export, что и в клиентской JavaScript-разработке. Это обеспечивает единообразный синтаксис модулей как на клиенте, так и на сервере.
Для использования синтаксиса ES6 Modules в Node.js необходимо убедиться, что среда его поддерживает. Начиная с версии Node.js 14, ES6 Modules стабильны, а с версии 16 они включены по умолчанию при наличии "type": "module". Вот как это настроить:
Обновите package.json: в файле package.json вашего Node.js-проекта добавьте следующую строку:
"type": "module"Это указывает Node.js обрабатывать файлы .js как ES6 Modules по умолчанию.
Расширения файлов: используйте .js для файлов модулей или явно указывайте .mjs, если хотите. Node.js распознаёт оба варианта, но при использовании настройки "type": "module" файлы .js будут считаться ES6 Modules.
// mathFunctions.js
export function add(a, b) {
return a + b;
}
// app.js
import { add } from './mathFunctions.js';
console.log(add(5, 3)); // 8В Node.js ESM относительные импорты требуют указания расширения файла (./mathFunctions.js, а не ./mathFunctions). Это отличается от CommonJS, где расширение необязательно.
Распространённые подводные камни
Некоторые особенности модулей могут застать новичков врасплох. Знание их заранее экономит время на отладку:
- Относительные пути в браузере требуют расширения.
import { add } from './math'работает во многих бандлерах, но не работает с нативным браузерным ESM, где необходимо писать'./math.js'. - Модули выполняются однократно и кэшируются. Независимо от того, сколько файлов импортируют один и тот же модуль, его код верхнего уровня выполняется один раз, и все импортёры разделяют один экземпляр. Именно это обеспечивает работу примера с общим состоянием выше.
- Импорты — это привязки только для чтения, а не копии. Переприсвоить импортированное значение нельзя (
add = 5вызовет ошибку). Вы видите живую ссылку на экспорт: если экспортирующий модуль изменит значение, импортёры увидят новое значение. import/exportдолжны находиться на верхнем уровне. Статические операторыimportиexportне могут находиться внутри блокаifили функции. Для условной загрузки используйте функцию динамическогоimport().- Модульные скрипты отложены. Тег
<script type="module">в браузере не блокирует парсинг; он выполняется после разбора HTML, аналогично добавлениюdefer.
Подробнее о постоянно включённом строгом режиме см. в разделе Строгий режим. Полный справочник по экспорту и импорту см. в разделе Экспорт и импорт.
Лучшие практики использования модулей JavaScript
- Простота: используйте чёткие и простые имена для файлов и экспортов.
- Последовательность: применяйте одинаковые шаблоны и структуры во всём проекте, чтобы код был предсказуемым.
- Документирование: комментируйте код и описывайте, как использовать ваши модули.
- Оптимизация по мере необходимости: регулярно пересматривайте и оптимизируйте код по мере роста проекта.
Полный пример
Ниже приводится полный пример, объединяющий всё, что вы узнали из этой статьи.
Структура проекта:
/src
/math
- mathFunctions.js
- app.js
index.htmlmathFunctions.js:
export const add = (a, b) => a + b;
export default function subtract(a, b) {
return a - b;
}app.js:
import subtract, { add } from './math/mathFunctions.js';
document.getElementById('add').addEventListener('click', function() {
const result = add(5, 3);
document.getElementById('result').textContent = `Adding: ${result}`;
});
document.getElementById('subtract').addEventListener('click', function() {
const result = subtract(5, 3);
document.getElementById('result').textContent = `Subtracting: ${result}`;
});index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JavaScript Module Example</title>
</head>
<body>
<button id="add">Add 5 + 3</button>
<button id="subtract">Subtract 5 - 3</button>
<div id="result"></div>
<script type="module" src="src/app.js"></script>
</body>
</html>Примечание: ES Modules требуют локального сервера разработки (например, npx serve или Vite) для работы в браузере из-за ограничений CORS. Открытие index.html напрямую через file:// завершится ошибкой.
Эта структура использует простую веб-страницу с кнопками для демонстрации сложения и вычитания чисел с помощью импортированных функций. Результаты отображаются непосредственно на странице.
Пояснение к примеру
- mathFunctions.js: файл содержит две функции (
addиsubtract), экспортируемые как модули.add— именованный экспорт,subtract— экспорт по умолчанию. - app.js: файл импортирует функции из
mathFunctions.jsи привязывает их к событиям нажатия кнопок для выполнения вычислений при взаимодействии пользователя со страницей. - index.html: HTML-файл создаёт пользовательский интерфейс с кнопками и областью для отображения результатов. Подключает
app.jsкак модуль.
Этот полный пример демонстрирует, как модули JavaScript можно структурировать и использовать в реальном приложении.
Заключение
Модули JavaScript — мощный инструмент для организации и поддержки крупномасштабных веб-приложений. Понимая и правильно используя различные системы модулей, вы можете повысить масштабируемость и удобство сопровождения своих проектов. Регулярное обновление знаний о синтаксисе модулей, лучших практиках и продвинутых техниках обеспечит актуальность ваших навыков разработки и конкурентоспособность проектов.