Когда-то давно программы были проще и меньше, чем сейчас. Как и их код. Когда код JavaScript был маленьким, хранить его в одном файле не составляло никакой сложности. Но по мере роста и усложнения программ код все рос и рос, и в результате держать его в одном файле стало сложно.
Так мы пришли к концепции модульности. Для ее реализации было придумано много путей. Например, AMD и UMD.
Но теперь они скорее часть истории. Их больше не используют, но вы можете их увидеть в некоторых старых приложениях.
Еще одна модульная система — CommonJS. Она была создана для Node.js-серверов. Далее мы поговорим об этой системе более подробно.
Модульная система на уровне языка появилась в 2015 году. Обычно ее называют ES6-модулями. Сейчас она поддерживается всеми основными браузерами и Node.js, так что о ней мы тоже поговорим.
Но для начала давайте разберем, что такое модули.
Что такое модули и для чего они нужны?
Модули — это просто строительные блоки, используемые при создании крупных приложений.
Основная идея модулей в том, что мы можем экспортировать часть кода, а затем импортировать ее для использования в других файлах.
То есть мы можем разбивать большие скрипты на более мелкие части, причем эти части (модули) могут использоваться в других местах программы.
Для лучшего понимания давайте рассмотрим иллюстрацию.
Как вы видите, на первой картинке все функции находятся в одном файле, поэтому он достаточно большой. А представьте, что в файле не три функции, а 20-30, плюс какой-то другой код. Такой файл может стать просто огромным. В нем будет сложно разобраться, да и поддерживать его тоже будет тяжело.
Поэтому мы разбиваем код на части (модули), как показано на второй картинке. Мы помещаем каждую функцию в отдельный модуль. После этого их можно импортировать в index.js, а также в другие модули.
Здесь index.js импортирует модули A, B и C, а кроме того модуль A импортирован модулем B. Теперь наш код просто понять и легко поддерживать, ведь все функции хранятся в разных модулях, а index.js — маленький.
Примечание. Модули в javaScript могут экспортировать переменные, функции, объекты и т. д.
Давайте рассмотрим некоторые достоинства модулей в JavaScript:
- Простота поддержки. Как мы уже говорили, если код разбит на части и хорошо организован, его легко поддерживать. Кроме того, при создании модулей их стараются сделать как можно более независимыми, чтобы они могли расти и развиваться обособленно. Благодаря этому при внесении изменений в модули не приходится вносить много изменений во всю остальную кодовую базу.
- Пригодность к переиспользованию. При помощи модулей мы можем использовать наш код снова и снова. Все, что нужно сделать, — экспортировать модуль из файла, и тогда все другие файлы в проекте смогут импортировать и использовать его.
- Возможность делиться кодом. Поскольку модули создаются с упором на обособленность, мы можем делиться ими с другими разработчиками, чтобы они их импортировали и использовали в собственных проектах. Один из лучших примеров распространения модулей — npm. С помощью npm можно импортировать пакеты, которыми поделились другие разработчики, и использовать их в своем проекте.
Введение в CommonJS Modules
Модульная система CommonJS — это стандарт, используемый для работы с модулями в Node.js.
Модули CommonJS загружаются синхронно и обрабатываются в порядке, в котором среда выполнения JavaScript их находит.
Стоит отметить, что эта система разрабатывалась для нужд бэкенда, поэтому для фронтенда она не подходит.
На стандарте модулей CommonJS базируется экосистема npm.
Давайте рассмотрим маленький пример того, как в CommonJS осуществляется импорт и экспорт.
Мы можем экспортировать любую функцию, класс, переменную и т. д., просто используя ключевое слово exports
:
// 📂 func.js exports.add = (a, b) => a + b;
После этого любой файл JavaScript сможет импортировать этот код. Синтаксис импорта:
const package = require('module-name')
Используя этот синтаксис, мы можем импортировать модуль при помощи ключевого слова require
:
// 📂 main.js const addModule = require('./func.js') addModule.add(2,4)
Прежде чем перейти к рассмотрению ES6модулей, давайте рассмотрим основные различия между модульной системой CommonJS и ES6-модулями.
- CommonJS используется для Javascript-кода в бэкенде, а ES6-модули — во фронтенде.
- CommonJS использует ключевое слово
exports
для экспорта иrequire
для импорта, а ES6-модули используютexport
для экспорта иimport
для импорта.
ES6-модули
Мы уже разобрали, что из себя представляют модули вообще. Давайте теперь поговорим о ES6-модулях.
ES6-модули используют ключевые слова:
export
— для экспорта функций, классов и т. п.import
— для импорта экспортированных модулей.
Рассмотрим пример. Здесь у нас есть три файла: index.html, func.js и main.js.
<!-- 📂 index.html --> <!DOCTYPE html> <html lang="en"> <head> <title>modules</title> <script type="module" src="./main.js"></script> </head> <body></body> </html>
// 📂 func.js export const sayHi = (user) => { console.log(`Hi!!! ${user}`); };
// 📂 main.js import { sayHi } from "./func.js"; sayHi("Alok"); // Hi!!! Alok
Как видите, файл func.js экспортирует (при помощи ключевого слова export
) функцию sayHi()
, которая просто выводит в консоль Hi !!! ${user}
.
А файл main.js импортирует (при помощи ключевого слова import
) ту же функцию sayHi()
из файла func.js. После этого мы можем запустить эту функцию со входящими данными «Alok», и в консоль выведется «Hi !!! Alok».
Как видите, мы не определяли функцию sayHi()
в файле main.js, но, тем не менее, мы можем ее использовать, поскольку она импортирована из файла func.js.
Примечание. Чтобы использовать модули, нам нужно при помощи соответствующего атрибута обозначить, что наш скрипт — это модуль. Мы это сделали в нашем index.html:
<script type="module" src="main.js"></script>
Импорт и экспорт
Есть разные способы импортировать и экспортировать ES6-модули. Выбор зависит от ваших нужд. Рассмотрим эти способы по порядку.
export перед объявлениями
Как вы видели в предыдущем примере, все, что нужно сделать для экспорта модуля, это поставить ключевое слово export
перед классом, массивом, функцией и т. д. — то есть перед тем, что нужно экспортировать.
После этого вы сможете импортировать этот код при помощи ключевого слова import
, за которым идет список того, что нужно импортировать. Список заключается в фигурные скобки: import {...}
.
Пример:
// 📂 func.js //exporting a function export const sayHi = (user) => { console.log(`Hi!!! ${user}`); }; //exporting a variable export let person = "Alok"; //exporting an array export let personArray = ["Alok", "Aman", "Rajan"]; // All are valid
// 📂 main.js //importing using a list of what to import import { sayHi,person,personArray } from "./func.js";
export, идущий отдельно от объявлений
Ключевое слово export также можно использовать отдельно. В этом случае мы можем экспортировать список разных вещей. После этого их можно будет импортировать точно так же, как мы уже делали.
Пример:
// 📂 func.js const sayHi = (user) => { console.log(`Hi!!! ${user}`); }; let person = "Alok"; let personArray = ["Alok", "Aman", "Rajan"]; // exporting all using a list export { sayHi, person, personArray };
// 📂 main.js //importing using a list of what to import import { sayHi,person,personArray } from "./func.js";
import *
До сих пор мы импортировали модули, указанные в списке. Но если нужно импортировать много модулей, их можно импортировать как объект. Для этого используется синтаксис import * as.
Пример:
// 📂 func.js const sayHi = (user) => { console.log(`Hi!!! ${user}`); }; let person = "Alok"; let personArray = ["Alok", "Aman"]; // exporting all using a list export { sayHi, person, personArray };
// 📂 main.js //importing using import * as <obj> import * as func from "./func.js"; //usage func.sayHi("Alok");// Hi!!! Alok console.log(func.person);// Alok console.log(func.personArray);// ["Alok", "Aman”]
import «as»
Мы также можем импортировать классы, переменные и т. д., используя для них другие имена. Например, можно импортировать переменную person
с именем user
. Для этого используется ключевое слово as
. (Слово «as» в переводе означает «как». То есть инструкция целиком звучит как «импорт person как user», — прим. перев.).
Пример:
// 📂 func.js const sayHi = (user) => { console.log(`Hi!!! ${user}`); }; let person = "Alok"; let personArray = ["Alok", "Aman"]; // exporting all using a list export { sayHi, person, personArray };
// 📂 main.js //importing using "as" import { sayHi as Hi, person as user, personArray } from "./func.js"; //usage Hi("Alok"); //Hi!!! Alok console.log(user); //Alok console.log(personArray); //["Alok", "Aman"]
export «as»
Аналогично, можно и экспортировать с другими именами. Для этого тоже используется ключевое слово as
.
Пример:
// 📂 func.js const sayHi = (user) => { console.log(`Hi!!! ${user}`); }; let person = "Alok"; let personArray = ["Alok", "Aman"]; //exporting using "as" export { sayHi as Hi, person as user, personArray };
// 📂 main.js //importing using a list import { Hi, user, personArray } from "./func.js"; //usage Hi("Alok"); //Hi!!! Alok console.log(user); //Alok console.log(personArray); //["Alok", "Aman"]
Экспорт по умолчанию
Мы можем сделать любой экспорт экспортом по умолчанию. Для этого используется ключевое слово default.
В основном, разработчики стараются держать в одном модуле какую-то одну функцию, класс и т. д., чтобы код оставался чистым. В таком случае можно использовать экспорт по умолчанию.
Если модуль экспортирован по умолчанию, его можно импортировать напрямую, не используя фигурные скобки.
Пример:
// 📂 func.js const sayHi = (user) => { console.log(`Hi!!! ${user}`); }; //exporting using default export default sayHi;
// 📂 main.js //importing without { } import sayHi from "./func.js"; sayHi("Alok"); // Hi!!! Alok
Обратите внимание, что при импорте sayHi()
мы так прямо и пишем sayHi
— не { sayHi }
.
Примечание. В одном файле можно использовать и экспорт по умолчанию, и именованные экспорты. Но экспорт по умолчанию может быть только один, как показано в примере:
// 📂 func.js const sayHi = (user) => { console.log(`Hi!!! ${user}`); }; let person = "Alok"; let personArray = ["Alok", "Aman"]; //exporting using default export default sayHi; //exporting using list export { person, personArray };
// 📂 main.js //importing without { } import sayHi from "./func.js"; //importing using a list import { person, personArray } from "./func.js"; //usage sayHi("Alok"); //Hi!!! Alok console.log(person); //Alok console.log(personArray); //["Alok", "Aman"]
Впрочем, как мы уже сказали, обычно разработчики стараются хранить все отдельно и иметь только один экспорт в модуле, чтобы поддерживать чистоту кода.
Несколько важных особенностей ES6-модулей
Всегда «use strict»
По умолчанию модули всегда используют строгий режим. Присвоение значения необъявленной переменной приведет к ошибке.
Пример:
<script type="module"> a = 5; {/* error */} </script>
Модульная область видимости
Модуль не имеет доступа к переменным высшего уровня и к функциям другого модуля.
Пример:
<script type="module"> {/* scope of person is only this module script */} let person = "Alok"; </script> <script type="module"> alert(person);{/* Error: person is not defined */} </script>
Код в модуле выполняется только при первом импорте
Если модуль импортируется несколькими другими модулями, его код будет выполнен только во время первого импорта. После выполнения кода экспортируемая функциональность будет роздана всем импортерам.
«this» в модуле не определен
В модулях this высшего уровня не определен (undefined).
Пример:
<script> alert(this); {/* global object */} </script> <script type="module"> alert(this); {/* undefined */} </script>
Вот и все! Я старался писать как можно более кратко. Спасибо за внимание!
Перевод статьи «Getting Started with JavaScript Modules».
[customscript]techrocks_custom_after_post_html[/customscript]
[customscript]techrocks_custom_script[/customscript]