Модулі в JavaScript: import/export
export віддає змінну, функцію або клас із модуля назовні, а import підключає експортовану сутність у іншому файлі. Разом вони утворюють вбудовану модульну систему, яка з'явилася в ES6 (2015) і дозволяє ділити код на файли-модулі та обмінюватися між ними значеннями, функціями, класами тощо.
Теорія
TL;DR
exportвідправляє сутність назовні,importзавантажує її всередину іншого модуля.- Іменованих експортів у файлі може бути скільки завгодно, імпортують їх у фігурних дужках і рівно за іменем.
export defaultдозволений один на файл, імпортується без дужок і під будь-яким іменем.- Перейменувати можна і на експорті, і на імпорті через
as, аimport * as nsзбирає весь модуль в один об'єкт. - Імпорти статичні: їх не можна писати всередині
if,forчи функції; для завантаження на вимогу єawait import(). - Модуль завантажується й кешується один раз, а працює це лише в модульному контексті (
type="module"в HTML або.mjsчи"type": "module"у Node.js).
Швидкий приклад
Файл math.js:
export const PI = 3.14;
export function add(a, b) {
return a + b;
}Файл app.js:
import { PI, add } from './math.js';
console.log(PI); // 3.14
console.log(add(2, 3)); // 5Іменований експорт і експорт за замовчуванням
Іменований експорт (export) дозволяє віддати назовні кілька сутностей з одного файлу.
// math.js
export const PI = 3.14;
export function multiply(a, b) {
return a * b;
}Імпортувати їх треба за іменем, у фігурних дужках, і імена мають збігатися:
import { PI, multiply } from './math.js';Експорт за замовчуванням (export default) віддає одну головну сутність файлу, найчастіше це «головний» об'єкт, функція або клас.
// math.js
export default function add(a, b) {
return a + b;
}Імпортувати його можна під будь-яким іменем і без фігурних дужок:
import sum from './math.js';
console.log(sum(2, 3)); // 5Порівняння двох форм:
| Критерій | export (іменований) | export default |
|---|---|---|
| Кількість на файл | Скільки завгодно | Тільки один |
| Імпорт | У фігурних дужках {} | Без фігурних дужок |
| Ім'я | Має збігатися | Може бути будь-яким |
| Перейменування при імпорті | Через as | Просто пишете інше ім'я |
| Приклад | import { add } from './file.js' | import add from './file.js' |
Три типові комбінації:
// math.js: іменований експорт
export const PI = 3.14;
export function add(a, b) { return a + b; }// app.js
import { PI, add } from './math.js';
console.log(add(1, 2)); // 3// math.js: експорт за замовчуванням (функція може бути анонімною)
export default function(a, b) { return a * b; }// app.js
import multiply from './math.js';
console.log(multiply(2, 3)); // 6// math.js: змішаний експорт
export const PI = 3.14;
export default function add(a, b) { return a + b; }// app.js
import add, { PI } from './math.js';
console.log(add(1, 2), PI); // 3 3.14Перейменування, імпорт усього модуля та реекспорт
Перейменувати сутність можна при експорті:
function sum(a, b) { return a + b; }
export { sum as add };і при імпорті:
import { add as plus } from './math.js';
plus(2, 3); // 5Весь модуль можна завантажити одним об'єктом, це зручно, коли експортів багато:
import * as math from './math.js';
console.log(math.PI); // 3.14
console.log(math.add(2, 3)); // 5Реекспорт дозволяє одному модулю «переупаковувати» інші, наприклад щоб зробити один вхідний файл для теки:
// utils.js
export { add, multiply } from './math.js';Тепер споживач бере все з одного місця:
import { add } from './utils.js';Правила роботи модулів
| Особливість | Опис |
|---|---|
Працює лише в модулях (type="module" в HTML або .mjs чи "type": "module" у Node.js) | Інакше буде SyntaxError |
| Імпорт і експорт завжди статичні | Їх не можна писати всередині if, for або функції |
| Модулі кешуються | Завантажуються один раз і далі перевикористовуються |
| Завантаження модулів асинхронне | Для завантаження на вимогу є await import() |
| Тіло модуля виконується у strict mode і має власну область видимості | Змінні модуля не потрапляють у глобальний об'єкт |
Іменовані імпорти це «живі» зв'язки (live bindings): якщо модуль пізніше змінить експортовану змінну, споживач побачить нове значення, а не копію старого.
Динамічний імпорт завантажує модуль на вимогу і повертає проміс:
const math = await import('./math.js');
console.log(math.add(2, 3)); // 5Він працює всередині async-функції або на верхньому рівні модуля, і саме на ньому будується code splitting.
ES modules проти CommonJS
| Критерій | ES modules (import / export) | CommonJS (require / module.exports) |
|---|---|---|
| Синтаксис | import x from './m.js' | const x = require('./m.js') |
| Коли резолвляться зв'язки | Статично, до виконання | Динамічно, під час виконання |
| Де працює | Браузер і Node.js | Історично Node.js |
| Завантаження | Асинхронне, є await import() | require синхронний |
| Що дає імпорт | Живі зв'язки на експортовані значення | Копію об'єкта module.exports на момент виклику |
| Tree shaking | Можливий завдяки статичності | Практично неможливий |
// CommonJS
const { add } = require('./math.js');
module.exports = { add };
// ES modules
import { add } from './math.js';
export { add };Підсумок
| Ключове слово | Що робить | Приклад |
|---|---|---|
export | Віддає змінні та функції назовні | export const PI = 3.14; |
export default | Віддає головну сутність файлу | export default class User {} |
import | Підключає експортовані сутності | import { PI } from './math.js'; |
Простіше кажучи:
exportце «віддати назовні»,importце «завантажити всередину», аexport defaultце «віддати одне головне».
Типові помилки
- Забути про модульний контекст. Звичайний
<script src="app.js">не розумієimport, потрібен<script type="module" src="app.js">, а в Node.js розширення.mjsабо"type": "module"уpackage.json. - Плутати дужки.
import { add } from './math.js'бере іменований експорт,import add from './math.js'бере default. Якщо в модулі немає default, друга форма дастьundefinedабо помилку збирача. - Помилятися в імені іменованого імпорту. Імена мають збігатися символ у символ; щоб назвати інакше, потрібен
as:import { add as plus } from './math.js'. - Писати кілька
export defaultв одному файлі. Дозволений рівно один, інакшеSyntaxError. - Ставити
importусередину умови чи функції. Статичний імпорт можливий лише на верхньому рівні модуля; для умовного завантаження єawait import(). - Забувати розширення файлу у відносному шляху. У браузері та в Node.js з ES modules
'./math'не працює, треба'./math.js', на відміну від звички з CommonJS і збирачів. - Чекати, що модуль виконається двічі. Модулі кешуються: скільки б файлів його не імпортувало, тіло модуля відпрацює один раз.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.