Модульное программирование
Чтобы продемонстрировать модульное программирование, создадим для примера следующие файлы:
index.html
functions.js
script.js
Создаем файл index.html
в текстовом редакторе и добавляем в него следующий код:
index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JavaScript Modules</title>
</head>
<body>
<h1>Answers</h1>
<h2><strong id="x"></strong> and <strong id="y"></strong></h2>
<h3>Addition</h3>
<p id="addition"></p>
<h3>Subtraction</h3>
<p id="subtraction"></p>
<h3>Multiplication</h3>
<p id="multiplication"></p>
<h3>Division</h3>
<p id="division"></p>
<script src="functions.js"></script>
<script src="script.js"></script>
</body>
</html>
Создаем файл functions.js
и добавляем в него следующий код:
functions.jsfunction sum(x, y) {
return x + y
}
function difference(x, y) {
return x - y
}
function product(x, y) {
return x * y
}
function quotient(x, y) {
return x / y
}
Создаем файл script.js
который определяет значения переменных x
и y
, применяет к их значениям математические функции и отображает результат:
script.jsconst x = 10
const y = 5
document.getElementById('x').textContent = x
document.getElementById('y').textContent = y
document.getElementById('addition').textContent = sum(x, y)
document.getElementById('subtraction').textContent = difference(x, y)
document.getElementById('multiplication').textContent = product(x, y)
document.getElementById('division').textContent = quotient(x, y)
Для веб-сайтов в которых используются несколько небольших скриптов это достаточно эффективный способ, просто разделить код по разным файлам. Однако с этим подходом связаны некоторые проблемы:
Загрязняется глобальное пространство имен
все переменные которые вы создали в своих сценариях:sum
,difference
и т.д., теперь находятся в глобальном объектеwindow
. И если вы попытаетесь использовать другую переменную с именемsum
, вызываемую в другом файле, то будет трудно разобраться, какое значение будет использоваться в текущем сценарии, поскольку все они будут использовать одну и ту же глобальную переменнуюwindow.sum
. Единственный способ сделать переменную приватной, это поместить ее в область действия функции. Однако при этом может возникнуть конфликт, между значениемx
атрибутаid
элемента DOM и переменнойvar x
Усложняется управление зависимостями
сценарии должны загружаться в заданном порядке следования сверху вниз, чтобы корректно обеспечить доступность всех переменных в нужное время. Сохранение сценариев в виде разных файлов создает иллюзию разделения, но по сути это то же самое, что сохранение всегоJavascript
кода вместе вinline
теге<script>
Стандарт ECMAScript 2015 поддерживает использование модулей JavaScript
.
Модуль представляет собой сборку кода bundle
и выступает в качестве интерфейса для обеспечения функциональных возможностей использования отдельных модулей. Модуль экспортирует
свой код и импортируется
для использования в другом коде. Модули полезны тем, что позволяют разработчикам повторно использовать код, при этом обеспечивают стабильный код и понятный интерфейс к нему, который могут использовать другие разработчики, при этом их использование не загрязняет глобальное пространство имен.
Модули
Ключевые слова import
и export
используются для работы с модулями в JavaScript:
import
применяется для чтения кода, экспортируемого из другого модуляexport
применяется для предоставления кода другим модулям
Чтобы продемонстрировать, как это можно использовать на практике, обновим файл functions.js
до модуля и экспортируем его функции. Для этого просто добавим ключевое слово export
перед каждой функцией, что автоматически сделает их код доступным для любого другого модуля. Добавим в файл следующий код:
functions.jsexport function sum(x, y) {
return x + y
}
export function difference(x, y) {
return x - y
}
export function product(x, y) {
return x * y
}
export function quotient(x, y) {
return x / y
}
Теперь в верхней части файла script.js
, нам необходимо использовать ключевое слово import
для извлечения (импорта) кода функций из модуля functions.js
.
Инструкции кода с ключевым словом import
всегда должны быть вверху файла перед любым другим кодом, при этом необходимо указать относительный ./
в нашем случае, либо абсолютный путь к импортируемому файлу модуля.
Добавьте следующий код в файл script.js
. Обратите внимание, что отдельные функции, импортируются из модуля путем помещения их имени в фигурные скобки:
script.jsimport { sum, difference, product, quotient } from './functions.js'
const x = 10
const y = 5
document.getElementById('x').textContent = x
document.getElementById('y').textContent = y
document.getElementById('addition').textContent = sum(x, y)
document.getElementById('subtraction').textContent = difference(x, y)
document.getElementById('multiplication').textContent = product(x, y)
document.getElementById('division').textContent = quotient(x, y)
Чтобы этот код загружался в браузере как модуль, а не как обычный скрипт, добавьте в файл index.html
к тегу script
атрибут type="module"
. Любой код, который использует инструкции import
или export
должен содержать указанный атрибут при подключении соответствующего файла модуля:
index.html<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>JavaScript Modules</title>
</head>
<body>
<h1>Answers</h1>
<h2><strong id="x"></strong> and <strong id="y"></strong></h2>
<h3>Addition</h3>
<p id="addition"></p>
<h3>Subtraction</h3>
<p id="subtraction"></p>
<h3>Multiplication</h3>
<p id="multiplication"></p>
<h3>Division</h3>
<p id="division"></p>
<script type="module" src="functions.js"></script>
<script type="module" src="script.js"></script>
</body>
</html>
Теперь вы сможете перезагрузить обновленную страницу, и ваш веб-сайт будет использовать нативный механизм модулей Javascript. Поддержка браузерами этой новой возможности достаточно высока, используйте сервис caniuse, чтобы узнать какие браузеры ее поддерживают. Модули должны использоваться на серверной стороне, которая настраивается локально на компьютере с помощью http-сервера или в сети Интернет с помощью хостинг-провайдера.
Модули отличаются от обычных скриптов несколькими существенными особенностями:
- Модули ничего не добавляют в
global
глобальную область видимости или в объектwindow
- Модули всегда выполняются в строгом режиме
strict mode
- Загрузка одного и того же модуля дважды в один и тот же файл не будет иметь никакого эффекта, поскольку модули выполняются только один раз
- Работа с модулями требует серверной среды выполнения кода
Модули широко используются при работе со сборщиками кода такими, как Webpack
, для обеспечения поддержки браузерами дополнительных возможностей языка, а также доступны для использования непосредственно в браузерах.
Именованный экспорт
Как уже говорилось ранее, использование синтаксиса export
позволяет выборочно импортировать именованные инструкции кода, которые экспортируются из модуля по имени. Например, рассмотрим следующую упрощенную версию файла functions.js
:
functions.jsexport function sum(x, y) {
return x + y;
}
export function difference(x, y) {
return x - y;
}
Этот код позволяет импортировать функции sum
и difference
по их имени, используя фигурные скобки:
script.jsimport { sum, difference } from "./functions.js";
const x = 10;
const y = 5;
document.getElementById("addition").textContent = sum(x, y);
document.getElementById("subtraction").textContent = difference(x, y);
Псевдонимы
Можно использовать синтаксис псевдонимов для переименования импортируемых функций. Эта возможность позволяет избежать конфликтов имен в импортирующем файле. В следующем примере функция sum
будет переименована в add
, difference
в subtract
. Вызов функции add()
эквивалентен вызову функции sum()
:
script.jsimport { sum as add, difference as subtract } from "./functions.js";
const x = 10;
const y = 5;
document.getElementById("addition").textContent = add(x, y);
document.getElementById("subtraction").textContent = subtract(x, y);
Объекты
Используя *
синтаксис, вы можете импортировать содержимое всего модуля в составе одного объекта. В этом случае функции sum
и difference
будут доступны как методы объекта mathFunctions
:
script.jsimport * as mathFunctions from "./functions.js";
document.getElementById("addition").textContent = mathFunctions.sum(1, 2); // 3
document.getElementById("subtraction").textContent = mathFunctions.difference(10, 3); // 7
Примитивные значения, выражения, определения функций, асинхронные функции, классы, экземпляры классов могут быть экспортированы, если у них есть свой идентификатор:
// Примитивные значения
export const number = 100
export const string = 'string'
export const undef = undefined
export const empty = null
export const obj = { name: 'Homer' }
export const array = ['Bart', 'Lisa', 'Maggie']
// Функциональное выражение
export const sum = (x, y) => x + y
// Определение функции
export function difference(x, y) {
return x - y
}
// Асинхронная функция
export async function getBooks() {}
// Класс
export class Book {
constructor(name, author) {
this.name = name
this.author = author
}
}
// Экземпляр класса
export const book = new Book('Lord of the Rings', 'J. R. R. Tolkien')
Экспорт по умолчанию
В предыдущих примерах мы успешно экспортировали несколько именованных инструкций кода, а затем импортировали их по отдельности или в составе одного объекта, в котором импортируемый код содержался в его свойствах и методах. Модули Javascript поддерживают возможность экспорта по умолчанию с использованием ключевого слова default
. Экспорт по умолчанию не использует фигурные скобки и содержимое модуля будет импортироваться непосредственно в идентификатор с заданным вами именем.
Перепишем код файла functions.js
в следующем виде:
functions.jsexport default function sum(x, y) {
return x + y
}
В файле script.js
импортируем функцию sum
по умолчанию, как это показано в коде ниже, в переменной sum
будет объект с экспортированным функциями:
script.jsimport sum from "./functions.js";
document.getElementById("addition").textContent = sum(1, 2); // 3
Использование этой конструкции кода может привести к неожиданным ошибкам, поскольку нет ограничений на то, что вы можете назначить в качестве имени идентификатора экспорта по умолчанию, и что, соответственно, будете импортировать в инструкции импорта. В следующем примере из модуля по умолчанию экспортируется функция sum
, а в переменную difference
в инструкции импорта будет фактически импортирована функция sum
:
script.jsimport difference from './functions.js'
difference(1, 2) // 3
По этой причине рекомендуется использовать именованный экспорт из модуля, так как экспорт по умолчанию не требует идентификатора для передачи в него экспортируемого кода. Экспортом же по умолчанию обычно экспортируются примитивные значения или анонимные функции. Ниже приведен пример объекта, экспортируемого по умолчанию:
export default {
name: 'Lord of the Rings',
author: 'J. R. R. Tolkien',
}
Импортировать объект book
можно следующим образом:
import book from './functions.js'
Точно так же в следующем примере демонстрируется экспорт по умолчанию анонимной стрелочной функции:
export default () => 'This function is anonymous'
Функция импортируется из модуля следующим образом:
import anonymousFunction from './functions.js'
Именованный экспорт и экспорт по умолчанию можно использовать совместно друг с другом, как в модуле из примера ниже, который экспортирует два именованных значения и одно значение (функцию) по умолчанию:
export const length = 10
export const width = 5
export default function perimeter(x, y) {
return 2 * (x + y)
}
Импортируются эти переменные и функция следующим образом:
import calculatePerimeter, { length, width } from './functions.js'
calculatePerimeter(length, width) // 30
Инициализация модуля без импорта его частей
Используется, когда необходимо выполнить импорт модуля для выполнения кода внутри него, но не импортировать какую-либо его часть:
import './modulePath/index.js';