Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

Синтаксис import/export

JavaScript (ES6 и новее)Модули ES6По умолчанию

Основная идея

ES6 модули — это встроенный в JavaScript способ организации кода через директивы import и export, позволяющий разделять код на независимые файлы с чётко определёнными интерфейсами.

Ключевые аспекты

  • Named export — экспорт именованных сущностей: export { name, age }
  • Default export — один главный экспорт на модуль: export default function
  • Named import — импорт конкретных сущностей: import { name } from './module'
  • Default import — импорт дефолтного экспорта: import User from './User'
  • Статичность — импорты анализируются до выполнения кода

Плюсы ES6 модулей

  • Нативная поддержка в браузерах и Node.js
  • Статический анализ для tree-shaking
  • Чёткая структура зависимостей
  • Изолированная область видимости

Минусы

  • Не работают напрямую в старых браузерах
  • Требуют сборщика или type="module" в браузере
  • Сложнее динамическая загрузка (нужен import())

Частые ошибки на собеседованиях

  • Путают default и named exports при импорте
  • Забывают фигурные скобки для named imports
  • Не понимают, что один файл может иметь и default, и named exports
  • Используют require синтаксис вместо import в ES6 контексте

Введение и проблематика

До появления ES6 в JavaScript не было встроенной модульной системы. Разработчики использовали различные паттерны и библиотеки: IIFE, CommonJS (Node.js), AMD (RequireJS). Это создавало проблемы совместимости и усложняло разработку.

ES6 (2015) представил нативную модульную систему с директивами import и export, которая стала стандартом для современной JavaScript-разработки.

ES6 модули работают в строгом режиме (strict mode) по умолчанию — не нужно писать 'use strict'.


Базовая теория

Структура модуля

Каждый файл .js или .mjs может быть модулем. Модуль имеет собственную область видимости — переменные не попадают в глобальный scope.

    • index.js
      • math.js
      • string.js
      • User.js
  • Два типа экспортов

    graph LR A[Модуль] --> B[Named Exports] A --> C[Default Export] B --> D["{ name, age, fn }"] C --> E["один на модуль"]

    Практические примеры

    Named Export (именованный экспорт)

    Code Example 1: Какие два способа именованного экспорта показаны в первых двух вкладках? Как правильно импортировать именованные экспорты?

    js
    // utils/math.js
    // Экспорт при объявлении
    export const PI = 3.14159;
     
    export function sum(a, b) {
      return a + b;
    }
     
    export class Calculator {
      add(a, b) {
        return a + b;
      }
    }

    Default Export (экспорт по умолчанию)

    Code Example 2: Чем отличается синтаксис импорта default export от named export? Можно ли использовать произвольное имя при импорте?

    js
    // models/User.js
    // Один default export на файл
    export default class User {
      constructor(name) {
        this.name = name;
      }
     
      greet() {
        return `Привет, я ${this.name}!`;
      }
    }
     
    // Альтернативный синтаксис
    class User {
      // ...
    }
    export default User;
     
    // Для функций и значений
    export default function() { /* ... */ }
    export default 42;

    Комбинирование named и default

    Code Example 3: Как в одном файле совместить default и named экспорты? Как выглядит импорт обоих типов в одной строке?

    js
    // services/api.js
    // Один файл может иметь оба типа экспорта
    export const BASE_URL = 'https://api.example.com';
    export const TIMEOUT = 5000;
     
    export default class ApiClient {
      constructor() {
        this.baseUrl = BASE_URL;
      }
     
      fetch(endpoint) {
        return fetch(this.baseUrl + endpoint);
      }
    }
    js
    // app.js
    // Импорт default и named вместе
    import ApiClient, { BASE_URL, TIMEOUT } from './services/api';
     
    console.log(BASE_URL); // 'https://api.example.com'
    const client = new ApiClient();

    Пограничные кейсы

    ⚠️

    Частая ошибка: забыть фигурные скобки при named import

    Code Example 4: Какая ошибка допущена в первом варианте импорта? Что произойдёт при выполнении?

    js
    // ❌ Неправильно — это пытается импортировать default
    import sum from './math';
     
    // ✅ Правильно для named export
    import { sum } from './math';

    Реэкспорт (Re-export)

    Code Example 5: Зачем нужен реэкспорт? Что делает синтаксис export { ... } from?

    js
    // utils/index.js — точка входа для модуля
    // Реэкспорт из других файлов
    export { sum, PI } from './math';
    export { formatDate } from './date';
    export { default as User } from '../models/User';
     
    // Реэкспорт всего
    export * from './helpers';
    js
    // app.js — чистый импорт из одной точки
    import { sum, PI, formatDate, User } from './utils';
    🚫

    Антипаттерн: экспорт изменяемых значений

    Code Example 6: Почему экспорт let считается антипаттерном? Что произойдёт при попытке изменить импортированную переменную?

    js
    // ❌ Плохо — экспорт let
    export let counter = 0;
     
    // Другой модуль НЕ может изменить counter напрямую
    // import { counter } from './module';
    // counter = 10; // TypeError: Assignment to constant variable
     
    // ✅ Лучше — экспортировать функции для изменения
    let counter = 0;
    export function getCounter() { return counter; }
    export function increment() { counter++; }

    Продвинутые аспекты

    Динамический импорт

    Code Example 7: Чем динамический import() отличается от статического import? Что возвращает import()?

    js
    // Статический import — загружается при старте
    import { heavy } from './heavy-module';
     
    // Динамический import() — загружается по требованию
    async function loadModule() {
      const module = await import('./heavy-module');
      module.heavy(); // использование
    }
     
    // Условная загрузка
    if (needFeature) {
      const { feature } = await import('./optional-feature');
      feature();
    }

    Импорт в браузере

    Code Example 8: Какой атрибут необходим в теге <script> для использования ES6 модулей в браузере?

    html
    <!-- type="module" обязателен -->
    <script type="module" src="app.js"></script>
     
    <script type="module">
      import { greet } from './utils.js';
      greet('Мир');
    </script>

    Import assertions (новое)

    Code Example 9: Для чего используется assert { type: ... } при импорте? Какие типы файлов можно импортировать таким образом?

    js
    // Импорт JSON (требует поддержки)
    import data from './data.json' assert { type: 'json' };
     
    // Импорт CSS модулей
    import styles from './styles.css' assert { type: 'css' };

    Плюсы и минусы

    АспектES6 ModulesCommonJS (require)
    Синтаксисimport/exportrequire/module.exports
    ЗагрузкаСтатическаяДинамическая
    Tree-shaking✅ Поддерживается❌ Ограничено
    Браузер✅ Нативно❌ Требует сборщик
    Async✅ Top-level await❌ Нет
    АнализДо выполненияВо время выполнения

    Вопросы интервьюера

    Q: В чём разница между named и default export?

    Named exports — именованные, их может быть много, импортируются в {}. Default export — один на модуль, импортируется без {}.

    Q: Можно ли в одном файле использовать оба типа экспорта?

    Да, можно: один default и сколько угодно named exports.

    Q: Почему ES6 модули лучше для tree-shaking?

    Import/export статичны — анализируются до выполнения. Сборщик видит, какие экспорты используются, и удаляет неиспользуемый код.

    Q: Как загрузить модуль динамически?

    Через import() — возвращает Promise: const module = await import('./module').

    Q: Что такое реэкспорт и зачем он нужен?

    Реэкспорт (export { x } from './module') позволяет создать единую точку входа для нескольких модулей.


    Источники

    Code Example 1: Named Export (именованный экспорт)

    ❓ Какие два способа именованного экспорта показаны в первых двух вкладках? Как правильно импортировать именованные экспорты?

    Tab 1 — Объявление с export:

    js
    // utils/math.js
    export const PI = 3.14159;
     
    export function sum(a, b) {
      return a + b;
    }
     
    export class Calculator {
      add(a, b) {
        return a + b;
      }
    }

    Tab 2 — Отдельный export:

    js
    // utils/math.js
    const PI = 3.14159;
     
    function sum(a, b) {
      return a + b;
    }
     
    class Calculator {
      add(a, b) {
        return a + b;
      }
    }
     
    export { PI, sum, Calculator };

    Tab 3 — Импорт:

    js
    // app.js
    import { PI, sum, Calculator } from './utils/math';
     
    console.log(PI);
    console.log(sum(2, 3));
     
    const calc = new Calculator();
    console.log(calc.add(10, 20));

    Code Example 2: Default Export (экспорт по умолчанию)

    ❓ Чем отличается синтаксис импорта default export от named export? Можно ли использовать произвольное имя при импорте?

    Tab 1 — Экспорт:

    js
    // models/User.js
    export default class User {
      constructor(name) {
        this.name = name;
      }
     
      greet() {
        return `Привет, я ${this.name}!`;
      }
    }
     
    class User {
      // ...
    }
    export default User;
     
    export default function() { /* ... */ }
    export default 42;

    Tab 2 — Импорт:

    js
    // app.js
    import User from './models/User';
    import MyUser from './models/User';
     
    const user = new User('Иван');
    console.log(user.greet());

    Code Example 3: Комбинирование named и default экспортов

    ❓ Как в одном файле совместить default и named экспорты? Как выглядит импорт обоих типов в одной строке?

    js
    // services/api.js
    export const BASE_URL = 'https://api.example.com';
    export const TIMEOUT = 5000;
     
    export default class ApiClient {
      constructor() {
        this.baseUrl = BASE_URL;
      }
     
      fetch(endpoint) {
        return fetch(this.baseUrl + endpoint);
      }
    }
    js
    // app.js
    import ApiClient, { BASE_URL, TIMEOUT } from './services/api';
     
    console.log(BASE_URL);
    const client = new ApiClient();

    Code Example 4: Ошибка при импорте named export

    ❓ Какая ошибка допущена в первом варианте импорта? Что произойдёт при выполнении?

    js
    import sum from './math';
     
    import { sum } from './math';

    Code Example 5: Реэкспорт (Re-export)

    ❓ Зачем нужен реэкспорт? Что делает синтаксис export { ... } from?

    js
    // utils/index.js
    export { sum, PI } from './math';
    export { formatDate } from './date';
    export { default as User } from '../models/User';
     
    export * from './helpers';
    js
    // app.js
    import { sum, PI, formatDate, User } from './utils';

    Code Example 6: Экспорт изменяемых значений

    ❓ Почему экспорт let считается антипаттерном? Что произойдёт при попытке изменить импортированную переменную?

    js
    export let counter = 0;
     
    let counter = 0;
    export function getCounter() { return counter; }
    export function increment() { counter++; }

    Code Example 7: Динамический импорт

    ❓ Чем динамический import() отличается от статического import? Что возвращает import()?

    js
    import { heavy } from './heavy-module';
     
    async function loadModule() {
      const module = await import('./heavy-module');
      module.heavy();
    }
     
    if (needFeature) {
      const { feature } = await import('./optional-feature');
      feature();
    }

    Code Example 8: Импорт в браузере

    ❓ Какой атрибут необходим в теге <script> для использования ES6 модулей в браузере?

    html
    <script type="module" src="app.js"></script>
     
    <script type="module">
      import { greet } from './utils.js';
      greet('Мир');
    </script>

    Code Example 9: Import assertions

    ❓ Для чего используется assert { type: ... } при импорте? Какие типы файлов можно импортировать таким образом?

    js
    import data from './data.json' assert { type: 'json' };
     
    import styles from './styles.css' assert { type: 'css' };