Модули
В видео есть неточность. Функция факториала, показанная в видео, использует цикл, а не рекурсию
Транскрипт урока
Представьте себе поселенцев, строящих свой первый маленький городок. В нём всего несколько зданий: пара домов, почта и вокзал. Он такой маленький, что люди могут указывать любое здание по названию: "давай встретимся у почты" или "я живу во втором доме", или "почему ты трезвый, уже 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", поэтому "точка-слеш" говорит интерпретатору смотреть в текущей директории.

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

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" перед чем угодно и это можно будет "импортировать" в другой файл. Тут мы экспортируем всё.

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

import { surfaceArea, square, pi, e } from './math.js';
Или импортировать всё сразу:

import * as mathematics from './math.js';
Тут говорится "импортировать весь модуль и назвать его "mathematics" в этом модуле". Теперь у нас есть доступ ко всему экспортированному из модуля math, но нам нужно сослаться на всё это через название "mathematics" таким способом:

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 что-нибудь". В данном случае мы экспортируем функцию surfaceArea.

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

import surfaceArea from './math.js';

const surfaceOfMars = surfaceArea(3390);
Экспортирующие и импортирующие механизмы включают больше функциональности, вроде изменения названий в процессе импортирования, множество типов экспорта из единого модуля и другое, но самое главное вы узнали.

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

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

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

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

Этот подход обеспечивает идентичное поведение import в среде браузера и на сервере с типовой конфигурацией.
Выводы
Вы можете дробить код на отдельные модули. В JavaScript один модуль — это один файл.

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

  1. Экспорт чего-то из модуля.
  2. Импорт в другой модуль.
Экспорт и два способа импорта
Вы можете сделать одну позицию экспортируемой по умолчанию.

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;
Можно также экспортировать функцию или константу без имени:

const pi = 3.14;
const e = 2.718;

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

export default (r) => {
  return 4 * pi * square(r);
};
Импортирование чего-то, что было экспортировано по умолчанию:

// Отсутствуют фигурные скобки
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 height = findHeight();

Какое значение будет "записано" в константу height после выполнения?

Верно!
Неверно
Неверно
Дальше
Проверить
Завершить тест
Дан такой код:

const someFunction = (x) => {
  return 10 * 42;
};


const y = someFunction(9281);

Что будет "сохранено" в константе y после выполнения?
Неверно
Верно!
Неверно
Дальше
Проверить
Завершить тест
Каким будет результат вызова функции?

 const firstNum = 10;
 const secondNum = 5;


 const sum = (z, g) => z + g;


 sum(firstNum, secondNum);
Верно!
Неверно! Попробуйте еще раз
Неверно! Попробуйте еще раз
Дальше
Проверить
Завершить тест
Дана такая функция:

const sum = (a, b, c) => {
  return a + b + c;
};

Можно ли в этой ситуации использовать сокращенный синтаксис записи функции?
Неверно
Верно!
Дальше
Проверить
Завершить тест
Пройти еще раз
Пройти еще раз
Пройти еще раз
Пройти еще раз
Упражнение
В файле myMathModule.js:

  1. Создайте функцию getTriangleArea(), которая принимает два аргумента h и b и вычисляет площадь треугольника по формуле A = 1/2 * h * b, где h — высота, а b — основание треугольника.

getTriangleArea(5, 10) === 25;
getTriangleArea(15, 12) === 90;
2. Экспортируйте функцию.
В файле solution.js:

  1. Импортируйте функцию getTriangleArea() из модуля myMathModule.
  2. Создайте функцию, которая принимает аргумент n и возвращает площадь треугольника высотой n и основанием n2/2. Для реализации этой функции используйте импортированную функцию getTriangleArea() и функцию square() (принимает число и возвращает его квадрат).
  3. Экспортируйте созданную функцию по умолчанию.