Автор оригинала: FreeCodeCamp Community Member.
Preethi Kasireddy
Если вы новичок в JavaScript, Jargon Alade «Module Bundlers vs. Модульные погрузчики», «WebPack vs. BroseRify» и «AMD против Commonjs» могут быстро стать подавляющим.
Система модуля JavaScript может быть запугивающей, но понимание того, что это жизненно важно для веб-разработчиков.
В этом посту я буду распаковывать эти модные слова для вас на простом английском (и несколько образцов кода). Я надеюсь, что вы найдете это полезным!
Примечание. Для простоты, это будет разделено на два раздела: часть 1 будет погружаться в объяснение, какие модули и почему мы их используем. Часть 2 (размещенная на следующей неделе) будет проходить через то, что это значит для подключения модулей и различных способов сделать это.
Часть 1: Может кто-нибудь, пожалуйста, объясните, какие модули снова?
Хорошие авторы разделяют свои книги в главы и разделы; Хорошие программисты разделили свои программы в модули.
Как и глава книги, модули – это просто кластеры слов (или кода, в зависимости от обстоятельств).
Хорошие модули, однако, очень находятся в себе с различными функциональными возможностями, позволяя им перетасоваться, удалять или добавляться по мере необходимости, не нарушая систему в целом.
Зачем использовать модули?
Есть много преимуществ для использования модулей в пользу разрыва, взаимозависимой кодовой базы. Самые важные, на мой взгляд, являются:
1) Осположимость: По определению, модуль является самостоятельным. Хорошо продуманный модуль направлен на уменьшение зависимостей на части кодовой базы максимально возможной, чтобы он мог расти и улучшаться самостоятельно. Обновление одного модуля намного проще, когда модуль отделен с других кусков кода.
Возвращаясь к примеру нашей книги, если вы хотите обновить главу в вашей книге, это будет кошмаром, если бы небольшое изменение в одной главе требовала вам также настроить любую другую главу. Вместо этого вы бы захотете написать каждую главу таким образом, что улучшения могут быть сделаны, не влияя на другие главы.
2) Наменение: В JavaScript переменные за пределами объема функции верхнего уровня являются глобальными (что означает, все могут получить доступ к ним). Из-за этого распространено иметь «загрязнение пространства имен», где совершенно несвязанный код допускает глобальные переменные.
Совместное использование глобальных переменных между несвязанным кодом является большой Нет-нет в развитии Отказ
Как мы увидим позже в этом посте, модули позволяют нам избежать загрязнения пространства имен, создавая личное пространство для наших переменных.
3) повторное использование: Давайте быть честенным здесь: мы все скопированные код, которые мы ранее писали в новые проекты в одной точке или иной. Например, давайте представим, что вы скопировали некоторые методы коммунальных услуг, которые вы написали из предыдущего проекта в ваш текущий проект.
Это все хорошо и хорошо, но если вы найдете лучший способ написать часть этого кода, который вы должны вернуться и помнить, чтобы обновить его повсюду, вы его написали.
Это, очевидно, огромная трата времени. Разве это не было бы намного проще, если бы было – ждать этого – модуль, который мы можем повторно использовать снова и снова?
Как вы можете включить модули?
Есть много способов включить модули в ваши программы. Давайте пройдем через несколько из них:
Модуль шаблона
Узор модуля используется для подражания концепции классов (поскольку JavaScript не поддерживает классы поддержки), чтобы мы могли хранить как публичные, так и частные методы, так и переменные внутри одного объекта – аналогично, как классы используются в других языках программирования, такие как Java или питон. Это позволяет нам создать Public Flighting API для методов, которые мы хотим разоблачить в мире, при этом все еще инкапсулируя частные переменные и методы в объеме закрытия.
Есть несколько способов выполнить шаблон модуля. В этом первом примере я буду использовать анонимное закрытие. Это поможет нам достичь нашей цели, поместив все наш код в анонимную функцию. (Помните: в JavaScript функции – единственный способ создать новый объем.)
Пример 1: анонимное закрытие
(function () { // We keep these variables private inside this closure scope var myGrades = [93, 95, 88, 0, 55, 91]; var average = function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item}, 0); return 'Your average grade is ' + total / myGrades.length + '.'; } var failing = function(){ var failingGrades = myGrades.filter(function(item) { return item < 70;}); return 'You failed ' + failingGrades.length + ' times.'; } console.log(failing()); }()); // 'You failed 2 times.'
С этой конструкцией наша анонимная функция имеет свою собственную среду оценки или «закрытие», а затем мы немедленно оцениваем ее. Это позволяет нам скрывать переменные из родительского (глобального) пространства имен.
Что хорошего в этом подходе, то есть то, что вы можете использовать локальные переменные внутри этой функции, не случайно перезаписывая существующие глобальные переменные, но все еще доступ к глобальным переменным, например:
var global = 'Hello, I am a global variable :)'; (function () { // We keep these variables private inside this closure scope var myGrades = [93, 95, 88, 0, 55, 91]; var average = function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item}, 0); return 'Your average grade is ' + total / myGrades.length + '.'; } var failing = function(){ var failingGrades = myGrades.filter(function(item) { return item < 70;}); return 'You failed ' + failingGrades.length + ' times.'; } console.log(failing()); console.log(global); }()); // 'You failed 2 times.' // 'Hello, I am a global variable :)'
Обратите внимание, что в скобках вокруг анонимной функции требуются, потому что утверждения, которые начинаются с ключевого слова Функция Всегда считаются декларациями функций (помните, что вы не можете иметь неназванные декларации функций в JavaScript.) Следовательно, окружающие скобки создают экспрессию функции. Если вам интересно, вы можете Читайте больше здесь Отказ
Пример 2: Глобальный импорт Еще один популярный подход, используемый библиотеками, как jquery это глобальный импорт. Это похоже на анонимное закрытие, которое мы только что увидели, за исключением теперь, когда мы проходим в глобалях как параметры:
(function (globalVariable) { // Keep this variables private inside this closure scope var privateFunction = function() { console.log('Shhhh, this is private!'); } // Expose the below methods via the globalVariable interface while // hiding the implementation of the method within the // function() block globalVariable.each = function(collection, iterator) { if (Array.isArray(collection)) { for (var i = 0; i < collection.length; i++) { iterator(collection[i], i, collection); } } else { for (var key in collection) { iterator(collection[key], key, collection); } } }; globalVariable.filter = function(collection, test) { var filtered = []; globalVariable.each(collection, function(item) { if (test(item)) { filtered.push(item); } }); return filtered; }; globalVariable.map = function(collection, iterator) { var mapped = []; globalUtils.each(collection, function(value, key, collection) { mapped.push(iterator(value)); }); return mapped; }; globalVariable.reduce = function(collection, iterator, accumulator) { var startingValueMissing = accumulator === undefined; globalVariable.each(collection, function(item) { if(startingValueMissing) { accumulator = item; startingValueMissing = false; } else { accumulator = iterator(accumulator, item); } }); return accumulator; }; }(globalVariable));
В этом примере GlobalVariabiable это единственная переменная, которая глобальная. Преимущество этого подхода над анонимными закрытиями состоит в том, что вы объявляете глобальные переменные APPFRONT, заставляя его кристально чистить людям, читающим ваш код.
Пример 3: Интерфейс объекта Еще один подход – создать модули с использованием автономного интерфейса объекта, например:
var myGradesCalculate = (function () { // Keep this variable private inside this closure scope var myGrades = [93, 95, 88, 0, 55, 91]; // Expose these functions via an interface while hiding // the implementation of the module within the function() block return { average: function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item; }, 0); return'Your average grade is ' + total / myGrades.length + '.'; }, failing: function() { var failingGrades = myGrades.filter(function(item) { return item < 70; }); return 'You failed ' + failingGrades.length + ' times.'; } } })(); myGradesCalculate.failing(); // 'You failed 2 times.' myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'
Как видите, этот подход позволяет нам решить, какие переменные/методы мы хотим сохранить частные (например, MyGrades ) и какие переменные/методы, которые мы хотим разоблачить, поместив их в оператор возврата (например, среднее & Неспособность ).
Пример 4: Выявление шаблона модуля Это очень похоже на вышеуказанный подход, за исключением того, что он гарантирует, что все методы и переменные хранятся в частном порядке, пока явно выясняется:
var myGradesCalculate = (function () { // Keep this variable private inside this closure scope var myGrades = [93, 95, 88, 0, 55, 91]; var average = function() { var total = myGrades.reduce(function(accumulator, item) { return accumulator + item; }, 0); return'Your average grade is ' + total / myGrades.length + '.'; }; var failing = function() { var failingGrades = myGrades.filter(function(item) { return item < 70; }); return 'You failed ' + failingGrades.length + ' times.'; }; // Explicitly reveal public pointers to the private functions // that we want to reveal publicly return { average: average, failing: failing } })(); myGradesCalculate.failing(); // 'You failed 2 times.' myGradesCalculate.average(); // 'Your average grade is 70.33333333333333.'
Это может показаться многое, но это просто вершина айсберга, когда дело доходит до модульных узоров. Вот несколько ресурсов, которые я нашел полезным в моих собственных исследованиях:
- Обучение дизайна JavaScript Design Addy Osmani: сокровищница деталей в впечатляющем кратком чтении
- Адекватно хорошее бен вишневое : полезный обзор с примерами расширенного использования модуля
- Блог Карла Дэнли : Обзор шаблона модуля и ресурсы для других шаблонов JavaScript.
Commonjs и AMD
Подходы, прежде всего, имеют одно общее: использование одной глобальной переменной для обертывания своего кода в функции, создавая тем самым, создавая саму по себе частное пространство имен, используя область замыкания.
Хотя каждый подход эффективен по-своему, у них есть свои недостатки.
Для одного, как разработчик, вам нужно знать порядок правильной зависимости для загрузки файлов. Например, скажем, вы используете Backbone в вашем проекте, поэтому вы включите тег сценария для исходного кода Backbone в вашем файле.
Тем не менее, поскольку позвоночник имеет жесткую зависимость от underscore.js, тег скрипта для багрового файла не может быть размещен перед файлом underscore.js.
Как разработчик, управление зависимостями и получение этих вещей, иногда может быть головной болью.
Другим недостатком является то, что они все еще могут привести к столкновениям пространства имен. Например, что если два ваших модуля имеют одно и то же имя? Или что, если у вас есть две версии модуля, и вам нужны?
Таким образом, вы, вероятно, задаетесь вопросом: можете ли мы разработать способ попросить интерфейс модуля, не проходя через глобальный объем?
К счастью, ответ да.
Существует два популярных и хорошо реализованных подхода: Commonjs и AMD.
Commonjs.
Commonjs – это волонтерская рабочая группа, которая проектирует и реализует APIS JavaScript для объявления модулей.
Модуль Commonjs по существу является многоразовым куском JavaScript, который экспортирует определенные объекты, что делает их доступными для других модулей для требуется в их программах. Если вы запрограммированы в Node.js, вы будете очень знакомы с этим форматом.
С помощью Commonjs каждый файл JavaScript хранит модули в своем собственном уникальном контексте модуля (так же, как упаковывать его в закрытие). В этой области мы используем Module.exports Объект для выставления модулей и требуется импортировать их.
Когда вы определяете модуль Commonjs, это может выглядеть что-то подобное:
function myModule() { this.hello = function() { return 'hello!'; } this.goodbye = function() { return 'goodbye!'; } } module.exports = myModule;
Мы используем специальный объектный модуль и поместите ссылку на нашу функцию в Module.exports Отказ Это позволяет системе модулей Commonjs знаю, что мы хотим разоблачить, чтобы другие файлы могли потреблять его.
Тогда когда кто-то хочет использовать MyModule Они могут потребовать его в их файле, как так:
var myModule = require('myModule'); var myModuleInstance = new myModule(); myModuleInstance.hello(); // 'hello!' myModuleInstance.goodbye(); // 'goodbye!'
Есть две очевидные преимущества для этого подхода по модульным модулям, которые мы обсуждали ранее:
1. Избегание глобального загрязнения пространства имен 2. Делать наши зависимости явные
Более того, синтаксис очень компактный, который я лично люблю.
Другое, что следует отметить, что Commandjs принимает сервер-первый подход и синхронно загружает модули. Это имеет значение, потому что если у нас есть три других модуля, нам нужно требуется это загрузит их один за другим.
Теперь, что отлично работает на сервере, но, к сожалению, затрудняет использование при записи JavaScript для браузера. Достаточно сказать, что чтение модуля из сети принимает Лот дольше, чем чтение с диска. До тех пор, пока скрипт для загрузки модуля запущен, он блокирует браузер из строя ничего другого, пока он не закончит загрузку. Он ведет себя так, потому что нить JavaScript останавливается, пока код не будет загружен. (Я охвачу, как мы можем работать по этому вопросу в части 2, когда мы обсуждаем пакет модуля. На данный момент это все, что нам нужно знать).
Amd.
Commonjs все хорошо и хорошо, но что, если мы хотим загружать модули асинхронно? Ответ называется асинхронным модулем определения или AMD для коротких.
Модули загрузки с помощью AMD выглядят что-то подобное:
define(['myModule', 'myOtherModule'], function(myModule, myOtherModule) { console.log(myModule.hello()); });
Что происходит вот то, что Определить Функция принимает в качестве первого аргумента массив каждого из зависимостей модуля. Эти зависимости загружаются на заднем плане (в неблокирующемся манере), а однажды загружены Определить Вызывает функцию обратного вызова, которую она была предоставлена.
Далее функция обратного вызова требует аргументов, зависимости, которые были загружены – в нашем случае MyModule и MyOthermodule – позволяя функции использовать эти зависимости. Наконец, сами зависимости также должны быть определены с использованием Определить ключевое слово.
Например, MyModule может выглядеть так:
define([], function() { return { hello: function() { console.log('hello'); }, goodbye: function() { console.log('goodbye'); } }; });
Поэтому опять же, в отличие от Commonjs, AMD принимает браузер – первый подход рядом с асинхронным поведением, чтобы выполнить работу. (Примечание, есть много людей, которые сильно считают, что динамически загружают файлы по частям, поскольку вы начинаете запускать код, не являются благоприятными, которые мы рассмотрим больше, когда в следующем разделе на модуле).
Помимо асинхроничности, еще одно преимущество AMD в том, что ваши модули могут быть объектами, функциями, конструкторами, строками, JSON и многие другие типы, в то время как Commonjs поддерживает только объекты в качестве модулей.
Как сказано, что AMD не совместима с IO, файловой системой и другими серверами, ориентированными на серверные функции, доступными через COMBERJS, и синтаксис функциональной упаковки – это немного более глубоко по сравнению с простым требуется утверждение.
Umd.
Для проектов, которые требуют поддержки функций AMD и Commonjs, есть еще один формат: Универсальный модуль определение (UMD).
UMD по существу создает способ использовать любой из двух, а также поддерживая глобальное определение переменной. В результате модули UMD способны работать на клиенте, так и на сервере.
Вот быстрый вкус того, как UM идет о своем бизнесе:
(function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD define(['myModule', 'myOtherModule'], factory); } else if (typeof exports === 'object') { // CommonJS module.exports = factory(require('myModule'), require('myOtherModule')); } else { // Browser globals (Note: root is window) root.returnExports = factory(root.myModule, root.myOtherModule); } }(this, function (myModule, myOtherModule) { // Methods function notHelloOrGoodbye(){}; // A private method function hello(){}; // A public method because it's returned (see below) function goodbye(){}; // A public method because it's returned (see below) // Exposed public methods return { hello: hello, goodbye: goodbye } }));
Для получения дополнительных примеров форматов UMD проверьте этот Просвещать репо на github.
Родной js.
Фу! Ты все еще рядом? Я не потерял тебя в лесу здесь? Хорошо! Потому что у нас есть еще один * тип модуля, чтобы определить, прежде чем мы закончим.
Как вы, вероятно, заметили, ни один из модулей выше не был родным для JavaScript. Вместо этого мы создали пути эмулировать Система модулей, используя либо шаблон модуля, Commonjs или AMD.
К счастью, умные люди на TC39 (корпус стандартов, которые определяют синтаксис и семантику ECMAScript), ввел встроенные модули с ECMAScript 6 (ES6).
ES6 предлагает различные возможности для импорта и экспорта модулей, которые другие сделали отличную работу, объясняющую – вот несколько из этих ресурсов:
Что отлично в отношении модулей ES6 по отношению к Commonjs или AMD – это то, как ему удается предложить лучшее из обоих миров: компактный и декларативный синтаксис и Асинхронная нагрузка, плюс добавленные преимущества, такие как лучшая поддержка циклических зависимостей.
Вероятно, моя любимая особенность модулей ES6 в том, что импорт жить Просмотры только для чтения видов экспорта. (Сравните это с Commonjs, где импорт являются копиями экспорта и, следовательно, не живы).
Вот пример того, как это работает:
// lib/counter.js var counter = 1; function increment() { counter++; } function decrement() { counter--; } module.exports = { counter: counter, increment: increment, decrement: decrement }; // src/main.js var counter = require('../../lib/counter'); counter.increment(); console.log(counter.counter); // 1
В этом примере мы в основном сделаем два копии модуля: один, когда мы его экспортируем, и один, когда нам это нужно.
Более того, копия в Main.js теперь отключается от оригинального модуля. Вот почему даже когда мы увеличиваем наш счетчик, он все еще возвращается 1 – потому что переменная счетчика, которую мы импортировали, представляет собой отключенную копию переменной счетчика из модуля.
Таким образом, увеличение счетчика пристегнут его в модуле, но не увеличит вашу скопируемую версию. Единственный способ изменить скопированную версию счетчика счетчика – это сделать вручную:
counter.counter++; console.log(counter.counter); // 2
С другой стороны, ES6 создает Live-Read – только вид модулей, которые мы импортируем:
// lib/counter.js export let counter = 1; export function increment() { counter++; } export function decrement() { counter--; } // src/main.js import * as counter from '../../counter'; console.log(counter.counter); // 1 counter.increment(); console.log(counter.counter); // 2
Прохладные вещи, а? То, что я нахожу действительно убедившись в проживании только для чтения, так это то, как они позволяют разделить свои модули на меньшие кусочки без потери функциональности.
Тогда вы можете повернуться и объединить их снова, нет проблем. Это просто “работает.”
С нетерпением жду: объединение модулей
Ух ты! Куда уходит время? Это была дикая поездка, но я искренне надеюсь, что это дало вам лучшее понимание модулей в JavaScript.
В следующем разделе я прогулюсь через патуку модуля, охватывающие темы ядра, включая:
- Почему мы раскладываем модули
- Разные подходы к объединению
- Модуль Ecmascript Module API
- …и больше.:)
ПРИМЕЧАНИЕ. Чтобы сохранить вещи простыми, я пропустил некоторые из Nitty-Gritty деталей (думаю: циклические зависимости) в этом посте. Если я оставил все важное и/или увлекательное, пожалуйста, дайте мне знать в комментариях!