JavaScript export и import
Именованные и дефолтные экспорты, псевдонимы as, пространство имён, динамический import(), реэкспорт и типичные ошибки — с примерами.
В современном JavaScript ключевые слова export и import позволяют разбить программу на несколько файлов и затем собрать их вместе. Файл, использующий их, является ES-модулем: вместо того чтобы сваливать всё в один огромный скрипт, каждую единицу функциональности держат в отдельном файле, объявляют то, чем она делится с внешним миром (export), и подключают только нужное в других местах (import).
Это руководство охватывает все формы, с которыми вы встретитесь на практике: именованные экспорты, экспорты по умолчанию, переименование через as, импорт пространства имён, реэкспорт и динамический import() — а также типичные ловушки. Каждый пример небольшой и запускаемый.
Зачем вообще нужны модули?
До появления ES-модулей совместное использование кода означало добавление всего в глобальный object и надежду на отсутствие конфликтов имён. Модули решают эту проблему:
- Инкапсуляция — всё, что вы не экспортируете, остаётся приватным для файла.
- Явные зависимости — строки
importв начале файла — это точный список того, на что он опирается. - Однократное выполнение — код верхнего уровня модуля выполняется один раз, сколько бы файлов его ни импортировало; результат кэшируется и используется совместно.
Чтобы запустить модули в браузере, загрузите входной файл с атрибутом type="module":
<script src="app.js" type="module"></script>В Node.js либо называйте файлы .mjs, либо задайте "type": "module" в package.json.
Именованные экспорты
Именованный экспорт передаёт привязку под её собственным именем. Модуль может иметь сколько угодно именованных экспортов.
// math.js
export const PI = 3.14159;
export function add(a, b) {
return a + b;
}
// You can also list exports at the bottom:
const subtract = (a, b) => a - b;
export { subtract };Именованные экспорты импортируются в фигурных скобках, с точно такими же именами:
// app.js
import { add, PI } from './math.js';
console.log(add(2, 3)); // 5
console.log(PI); // 3.14159Имена должны совпадать с именами при экспорте. import { Add }, когда экспортируется add, вызывает SyntaxError — такого именованного экспорта не существует.
Переименование с помощью as
Когда два модуля экспортируют одно и то же имя или имя неудобно, переименуйте его при импорте (или экспорте) с помощью as:
// Rename on import
import { add as sum } from './math.js';
console.log(sum(1, 1)); // 2// Rename on export
const internalAdd = (a, b) => a + b;
export { internalAdd as add };Экспорты по умолчанию
Экспорт по умолчанию — это единственная «главная» сущность, которую предоставляет модуль. Модуль может иметь не более одного экспорта по умолчанию.
// multiply.js
export default function multiply(a, b) {
return a * b;
}При импорте значения по умолчанию фигурные скобки не используются, а локальное имя выбирается произвольно:
// app.js
import multiply from './multiply.js'; // any name works
console.log(multiply(4, 5)); // 20Поскольку имя выбирает импортирующий файл, экспорты по умолчанию часто применяются, когда файл представляет собой одну сущность — единственный класс, единственный React-компонент, единственный объект конфигурации.
Совмещение экспорта по умолчанию и именованных экспортов
Модуль может содержать и то и другое. Экспорт по умолчанию указывается вне фигурных скобок, именованные — внутри:
// user.js
export default class User { /* ... */ }
export const ADMIN_ROLE = 'admin';// app.js
import User, { ADMIN_ROLE } from './user.js';По умолчанию предпочитайте именованные экспорты. Они делают импорты самодокументируемыми и позволяют инструментам (автодополнение, «найти ссылки», tree-shaking) работать надёжно. Прибегайте к экспорту по умолчанию только тогда, когда файл действительно экспортирует одну вещь.
Способы импорта
Импорт пространства имён (import * as)
Чтобы получить всё, что экспортирует модуль, в виде единого object, используйте импорт пространства имён:
// app.js
import * as math from './math.js';
console.log(math.add(5, 3)); // 8
console.log(math.PI); // 3.14159Каждый именованный экспорт становится свойством объекта math. Экспорт по умолчанию, если он есть, появляется как math.default. Используйте это, когда модуль объединяет множество связанных вспомогательных функций.
Динамический импорт (import())
Инструкция import выше является статической — она выполняется раньше всего остального, и её путь должен быть строковым литералом. Иногда требуется загружать модуль по требованию: только когда пользователь открывает какую-то функциональность или в зависимости от условия времени выполнения. Для этого используйте import() как функцию. Она возвращает Promise, который разрешается в объект пространства имён модуля:
// Load a heavy module only when needed
async function openEditor() {
const editor = await import('./editor.js');
editor.init();
}Динамический импорт работает внутри функций и условных операторов — в местах, где статический import не допускается, — что делает его основой разбивки кода и ленивой загрузки.
Реэкспорт
«Барельный» файл может собирать экспорты из нескольких модулей и перенаправлять их, чтобы потребители импортировали из одного места:
// shapes/index.js
export { Circle } from './circle.js';
export { Square } from './square.js';
export { default as Triangle } from './triangle.js'; // re-export a default as a name// app.js
import { Circle, Square, Triangle } from './shapes/index.js';Типичные ошибки
- Расширения важны в браузере и в Node.js ESM.
import { x } from './math'завершится ошибкой; пишите'./math.js'. import/exportработают только на верхнем уровне модуля — не внутри блокаifили функции. Для условной загрузки используйте динамическийimport().- Импорты являются живыми привязками только для чтения. Можно читать импортированное значение, но не переприсваивать его;
add = 5для импорта вызовет ошибку. - Экспорт по умолчанию не получает имя автоматически.
export default addэкспортирует значение, а не имяadd; локальное имя выбирает импортирующая сторона. - Модули откладываются и автоматически выполняются в строгом режиме —
'use strict'указывать не нужно.
Практический пример: мини-система библиотеки
Здесь несколько модулей каждый экспортирует array объектов книг (objects), а главный файл импортирует и объединяет их.
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Interactive Library App</title>
</head>
<body>
<h1>Interactive Library App</h1>
<button id="loadFiction">Load Fiction</button>
<button id="loadSciFi">Load Sci-Fi</button>
<div id="bookList"></div>
<script src="app.js" type="module"></script>
</body>
</html>fiction.js:
export const fictionBooks = [
{ title: 'Pride and Prejudice', author: 'Jane Austen' },
{ title: 'To Kill a Mockingbird', author: 'Harper Lee' }
];sciFi.js:
export const sciFiBooks = [
{ title: 'Dune', author: 'Frank Herbert' },
{ title: 'Neuromancer', author: 'William Gibson' }
];app.js:
import { fictionBooks } from './fiction.js';
import { sciFiBooks } from './sciFi.js';
function displayBooks(books) {
const list = document.getElementById('bookList');
list.innerHTML = '';
books.forEach(book => {
const item = document.createElement('div');
item.textContent = `${book.title} by ${book.author}`;
list.appendChild(item);
});
}
document
.getElementById('loadFiction')
.addEventListener('click', () => displayBooks(fictionBooks));
document
.getElementById('loadSciFi')
.addEventListener('click', () => displayBooks(sciFiBooks));Каждая категория живёт в своём модуле и экспортирует функцию или данные, а app.js объединяет их. Разделение данных и поведения таким образом делает каждый файл небольшим и позволяет легко добавлять новые категории.
Лучшие практики
- Предпочитайте именованные экспорты — импорты становятся самодокументируемыми, и tree-shaking работает корректно.
- Одна ответственность на модуль — файл должно быть легко описать одним предложением.
- Используйте барельные файлы умеренно — они упорядочивают импорты, но при злоупотреблении могут ухудшить tree-shaking.
- Ленивно загружайте тяжёлый или редко используемый код с помощью динамического
import(). - Всегда указывайте расширения файлов в относительных путях.
Заключение
Именованные экспорты дают несколько привязок на файл, экспорты по умолчанию — одну «главную» привязку, а import (с as, * as и динамическим import()) даёт точный контроль над тем, что и когда подключается. Вместе они превращают набор скриптов в поддерживаемый, tree-shakeable граф модулей. Далее узнайте, как модули загружаются в браузере, в разделе Модули, введение.