Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Модули Основы JavaScript

Представьте себе поселенцев, строящих свой первый маленький городок. В нём всего несколько зданий: пара домов, почта и вокзал. Он такой маленький, что люди могут указывать любое здание по названию: "давай встретимся у почты" или "я живу во втором доме", или "почему ты трезвый, уже 11 часов дня… пойдём ко мне… в дом номер 1".

С ростом города строилось больше зданий. Вскоре людям пришлось делать выбор:

  • давать каждому новому зданию уникальный номер или название
  • дробить город на улицы

Конечно, они могли использовать первый способ и просто давать уникальные номера и названия новым строениям, чтобы никогда не было двух зданий с номером 5. Это подходящее, но не самое гениальное решение, особенно для крупного города. Мой адрес: "Нью-Йорк, здание 654 931". Звучит как ерунда.

Большинство выбирает второй путь: делить город на улицы, обычно — прямые линии, и идентификаторы домов — названия или цифры — становятся уникальными в пределах улицы. В городе много зданий с номером 5, но они все расположены на разных улицах, поэтому всё в порядке.

Именно поэтому в вашем компьютере есть директории. Без них все ваши файлы хранились бы в одном месте, где вы не смогли бы хранить два файла с одинаковым названием. Когда есть директории, то создавая новый файл, вам нужно заботиться об именах только в текущей директории.

Эта система деления чего угодно на улицы, блоки или директории позволяет нам объединять вещи в значимые модули. Уолл-стрит — это улица банков и финансов. Своеобразный модуль Нью-Йорка с собственной задачей. У вас есть директория "Видео" и вы знаете, что она только для особого типа файлов - видео.

Первые программисты были поселенцами в новом и странном мире компьютеров. Они столкнулись с подобной проблемой и встали перед выбором.

Они могли писать весь код в едином файле и тогда всё — численные и строковые константы, переменные и функции должны были бы иметь уникальные имена. А если бы они захотели переиспользовать какой-то код в другом проекте, им бы потребовалось копировать его из этого гигантского файла и вставлять его в другой гигантский файл.

Или они могли бы пойти вторым путем: разделить код на небольшие модули. Модули могут храниться в отдельных файлах, а имена функций и констант будут уникальными только в пределах файла, но не во всей программе. И модули можно легко переиспользовать в разных проектах, без копирования и вставки.

У разных языков программирования разные подходы к этой задаче. В JavaScript он довольно простой: один файл — один модуль. Все упражнения, которые вы выполняете в этом курсе — это написание модулей.

Иногда нам нужно объединять код из разных файлов. Если вы просто напишете код в разных файлах, интерпретатор JavaScript не поймёт как получить что-то из другого файла.

Экспорт и два способа импорта

Давайте посмотрим на пример, где у нас есть файл math.js:

const pi = 3.14;
const e = 2.718;

const square = (x) => {
  return x * x;
};

const surfaceArea = (r) => {
  return 4 * pi * square(r);
};

Это наша крошечная математическая библиотека.

Создадим другой файл под именем planets.js со следующим содержанием:

const surfaceOfMars = surfaceArea(3390);
const surfaceOfMercury = surfaceArea(2440);
const yearSquared = square(2017);

Код здесь не будет работать, потому что surfaceArea и square здесь не существуют. Они в другом файле, о котором JavaScript пока не знает. Нам нужно сказать ему заглянуть в другой файл. Это называется "импортом" — давайте импортируем именно те функции, что нам нужны, по их имени:

import { surfaceArea, square } from './math.js';

const surfaceOfMars = surfaceArea(3390);
const surfaceOfMercury = surfaceArea(2440);
const yearSquared = square(2017);

Ключевое слово import, затем список того, что мы хотим, в фигурных скобках, а затем название модуля. Файл в той же директории, что и planets.js, поэтому './math.js' означает "взять из файла math.js, расположенного в той же (текущей) директории".

Когда вы импортируете что-то из Китая, что происходит в Китае? Верно, экспорт. Поэтому наша математическая библиотека должна исполнить свою роль — "экспортировать".

Поставьте export перед тем, что вы хотите экспортировать. Такая операция сделает это импортируемым куда угодно:

export const pi = 3.14;
export const e = 2.718;

export const square = (x) => {
  return x * x;
};

export const surfaceArea = (r) => {
  return 4 * pi * square(r);
};

Просто укажите export перед чем угодно и это можно будет "импортировать" в другой файл. Тут мы экспортируем всё. Ещё один способ экспортировать несколько значений:

const pi = 3.14;
const e = 2.718;

const square = (x) => {
  return x * x;
};

const surfaceArea = (r) => {
  return 4 * pi * square(r);
};

export { pi, e, square, surfaceArea };

Возвратимся к planets.js. Допустим, мы хотим импортировать что-то еще. Мы можем просто добавить названия в список:

import { surfaceArea, square, pi, e } from './math.js';

Или импортировать всё сразу:

import * as mathematics from './math.js';

Это значит: "импортировать весь модуль и назвать его mathematics в этом модуле". Вот почему к импортированным сущностям обращение происходит через mathematics, например, mathematics.surfaceArea:

import * as mathematics from './math.js';

const surfaceOfMars = mathematics.surfaceArea(3390);
const surfaceOfMercury = mathematics.surfaceArea(2440);
const yearSquared = mathematics.square(2017);

mathematics + точка + имя функции.

Теперь, если мы добавим функцию square в этот же файл, никакого конфликта не возникнет:

import * as mathematics from './math.js';

const square = (x) => {
  return x * x * x;
};

const yearSquared = mathematics.square(2017);   // 4068289
const weirdSquare = square(2017);               // 8205738913

В этом примере в первом случае была вызвана функция возведения в квадрат из модуля math, а во втором случае был вызов местной функции неправильного возведения в квадрат.

Экспорт по умолчанию

И последнее: часто вам требуется экспортировать из модуля что-то одно. Существует специальный механизм, который называется "экспорт по умолчанию", и вы можете экспортировать с помощью него только что-то одно. Но экспортированную по умолчанию вещь проще импортировать.

const pi = 3.14;
const e = 2.718;

const square = (x) => {
  return x * x;
};

const surfaceArea = (r) => {
  return 4 * pi * square(r);
};

export default surfaceArea;

Можно также экспортировать функцию или константу без имени:

export default (r) => {
  return 4 * pi * square(r);
};

Просто напишите код, как обычно, без специально указанных экспортов, а в конце выполните export default что-нибудь. В данном случае мы экспортируем функцию surfaceArea.

Импорт по умолчанию выглядит так:

// Без фигурных скобок
import surfaceArea from './math.js';

const surfaceOfMars = surfaceArea(3390);

При экспорте функции без имени, её имя в модуле будет определяться в момент импорта, т.е. один и тот же экспорт может иметь разные имена в разных модулях:

math.js

export default () => {
  ///
};

import1.js:

import something1 from './math.js';

import2.js:

import something2 from './math.js';

Экспорт по умолчанию может сочетаться с обычным экспортом, а экспортировать элементы можно по отдельности:

const pi = 3.14;
const e = 2.718;

const square = (x) => {
  return x * x;
};

const surfaceArea = (r) => {
  return 4 * pi * square(r);
};

export { e, pi };

export { square };

export default surfaceArea;

Импорт может выглядеть так:

import surfaceArea, { square, e, pi } from './math.js';

Ключевое слово as позволяет задать псевдоним для импортируемой сущности. Благодаря этому появляется возможность импортировать элементы с одинаковыми именами:

import { square, e, pi } from './math.js';
import { square as square1, e as e1, pi as pi1 } from './math1.js';

Расширение файла при импорте модуля

Указание расширения файла гарантирует, что он будет проанализирован как модуль такими средами выполнения, как Node.js и d8, и инструментами сборки, такими как Babel. И, если Babel разрешает опустить расширение файла, то официальная документация Node.js устанавливает конкретное требование:

При использовании ключевого слова import необходимо указать расширение файла. Пути каталогов (например, './startup/index.js') также должны быть полностью указаны.

Этот подход обеспечивает идентичное поведение import в среде браузера и на сервере с типовой конфигурацией.

Резюме

Вы можете дробить код на отдельные модули. В JavaScript один модуль — это один файл.

Объединение кода, расположенного в разных модулях, происходит через:

  1. Экспорт чего-то из модуля
  2. Импорт в другой модуль

Почти все уроки этого и других курсов на Хекслете используют модули. Такой подход максимально приближает нас к реальной жизни, когда проекты состоят из сотен, тысяч файлов и библиотек, которые пользуются друг другом.

При работе с модулями будет полезным сразу наработать некоторые модели поведения, позволяющие вам с легкостью определять, какой код вам доступен на исполнение, откуда пришел этот код и как его увидеть.

Ниже описан основной алгоритм, по которому нужно анализировать файл с кодом, над которым вы сейчас работаете. Этот алгоритм не является специфичным для работы в среде Хекслета, так нужно делать в принципе:

  1. Внимательно изучите все импорты, описанные в начале файла. Так вы узнаете, какие модули и функции доступны внутри вашего файла (не считая глобальных функций и модулей, которые доступны и без импорта, например, Math)
  2. Попробуйте классифицировать импортируемые функции. Если импорт выглядит так from './...', то есть содержит ./, значит импортируется модуль, содержимое которого находится в текущей файловой системе. Это автоматически означает несколько вещей. Первое: вы всегда можете открыть этот файл и посмотреть, что там написано. Второе: вы не сможете импортировать этот модуль в другой среде (ведь этого файла там нет)
  3. Если from 'name' содержит только имя, без ./ в начале, значит модуль подгружается либо из стандартной библиотеки nodejs, либо из установленных пакетов. Визуально невозможно отличить одно от другого. Попробуйте загуглить имя таким способом: "nodejs name". Если в выдаче будет ссылка на официальную документацию, значит это модуль nodejs; если на репозиторий npm — значит, это обычный пакет, который почти наверняка лежит на гитхабе, что можно проверить таким запросом: "github js name", где "name" это имя пакета

Дополнительные материалы

  1. Тонкости модульной системы ECMAScript 2015 (ES6)
  2. Node.js documentation: EcmaScript Modules
  3. V8: JavaScript modules
  4. Understanding ES6 Modules
  5. Exploring JS: Modules

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты.

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы

С нуля до разработчика. Возвращаем деньги, если не удалось найти работу.

Иконка программы Фронтенд-разработчик
Профессия
Разработка фронтенд-компонентов для веб-приложений
6 октября 10 месяцев
Иконка программы Node.js-разработчик
Профессия
Разработка бэкенд-компонентов для веб-приложений
6 октября 10 месяцев
Иконка программы Fullstack-разработчик
Профессия
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
6 октября 16 месяцев

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и соглашаетесь с «Условиями использования»