Модульная разработка
преимущество
- В модульной разработке файл обычно представляет собой модуль, который имеет свою собственную область видимости, предоставляет только определенные переменные и функции и может быть загружен по запросу.
- Зависимости загружаются автоматически и загружаются по запросу.
- Повысьте уровень повторного использования кода, упростите управление кодом и сделайте управление кодом более понятным и стандартизированным.
- Сокращены конфликты имен и устранены глобальные переменные.
- В настоящее время популярные спецификации модульности js включают модульные системы CommonJS, AMD, CMD и ES6.
Общие модульные характеристики
- CommonJs (Node.js)
- AMD (RequireJS)
- CMD (SeaJS)
CommonJS(Node.js)
CommonJS — это спецификация серверных модулей., Node.js принимает эту спецификацию.
согласно сCommonJS
Спецификация, один файл — это модуль, каждый модуль — это отдельная область, переменные (также функции и классы), определенные в файле,все частные, невидимый для других файлов.
Спецификация CommonJS загружает модули синхронно, то есть только после завершения загрузки можно выполнять последующие операции.
CommonJS
, загрузите модуль, используяrequire
метод. Этот метод читает файл, выполняет его и, наконец, возвращает внутреннийexports
объект.
Node.js
Он в основном используется для серверного программирования.Загружаемые файлы модулей, как правило, уже существуют на локальном жестком диске, и они загружаются быстро, без учета асинхронного метода загрузки, поэтомуCommonJS
Спецификация синхронно загружаемого модуля более применима.Но если это браузерная среда, то для загрузки модулей с сервера необходимо использовать асинхронный режим. Так что есть
AMD
,CMD
и другие решения.
var x = 5;
var addX = function(value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
// 也可以改写为如下
module.exports = {
x: x,
addX: addX,
};
let math = require('./math.js');
console.log('math.x',math.x);
console.log('math.addX', math.addX(4));
Определение асинхронного модуля AMD (RequireJS)
-
AMD
=Asynchronous Module Definition
,Прямо сейчасОпределение асинхронного модуля. AMD
Каноническая загрузка модулей является асинхронной и позволяет выполнять обратные вызовы функций, не дожидаясь загрузки всех модулей, а последующие операции могут выполняться в обычном режиме.-
AMD
в использованииrequire
Получить зависимые модули, использоватьexports
экспортAPI
.
//规范 API
define(id?, dependencies?, factory);
define.amd = {};
// 定义无依赖的模块
define({
add: function(x,y){
return x + y;
}
});
// 定义有依赖的模块
define(["alpha"], function(alpha){
return {
verb: function(){
return alpha.verb() + 1;
}
}
});
Асинхронная загрузка и обратные вызовы
require([module], callback)середина
callback
Функция обратного вызова после завершения загрузки модуля
//加载 math模块,完成之后执行回调函数
require(['math'], function(math) {
 math.add(2, 3);
});
RequireJS
RequireJS
Это библиотека инструментов для интерфейсного модульного управления, следующаяAMD
Технические характеристики,RequireJS
правдаAMD
Разработка спецификации.
RequireJS
Основная идея состоит в том, чтобы загрузить все требуемые или зависимые модули через функцию, а затем вернуть новую функцию (модуль). Весь последующий бизнес-код нового модуля работает внутри этой функции.
RequireJS
Каждый модуль необходимо поместить в отдельный файл и использоватьdefine
определить модули, использоватьrequire
Модуль вызова метода.
В зависимости от того, есть ли зависимости от других модулей, его можно разделить наавтономный модульа такжеЗависимый модуль.
- Независимый модуль, не зависящий от других модулей, определяемый напрямую
define({
method1: function(){},
method2: function(){}
});
//等价于
define(function() {
return {
method1: function(){},
method2: function(){}
}
});
- Ненезависимые модули, зависящие от других модулей
define([ 'module1', 'module2' ], function(m1, m2) {
...
});
//等价于
define(function(require) {
var m1 = require('module1');
var m2 = require('module2');
...
});
-
require
модуль вызова метода
require(['foo', 'bar'], function(foo, bar) {
foo.func();
bar.func();
});
CMD (SeaJS)
CMD
= Common Module Definition
,Прямо сейчасОбщее определение модуля.CMD
даSeaJS
Нормализованный вывод определения модуля в процессе продвижения.
Спецификация CMD похожа на AMD, обе в основном работают на стороне браузера, и метод записи очень похож. Основное отличие в том, чтоВремя инициализации модуля
- В AMD, пока модуль используется как зависимость, он будет загружен и инициализирован.
- В CMD модули инициализируются как зависимости и ссылаются на них, иначе они будут только загружены.
-
CMD
Уважая близость зависимости,AMD
Положитесь на предварительное уважение. -
AMD
изAPI
По умолчанию используется один, когда несколько,CMD
Строгое различие выступает за единую ответственность. Например,AMD
внутриrequire
Делятся на глобальные и локальные. В CMD нет глобальногоrequire
,поставкаseajs.use()
Осуществить загрузку и запуск модульной системы.CMD
в каждомAPI
Все просто и чисто.
//AMD
define(['./a','./b'], function (a, b) {
//依赖一开始就写好
a.test();
b.test();
});
//CMD
define(function (requie, exports, module) {
//依赖可以就近书写
var a = require('./a');
a.test();
...
//软依赖
if (status) {
var b = requie('./b');
b.test();
}
});
Sea.js
Используя Sea.js, при записи файлов вам необходимо следовать спецификации определения модуля CMD (Common Module Definition). Файл — это модуль.
Применение
- пройти через
exports
Выложите интерфейс. Это означает, что нет необходимости в пространствах имен, не говоря уже о глобальных переменных. Это полное разрешение конфликта имен. - пройти через
require
Импорт зависимостей. Это позволяет использовать встроенные зависимости, разработчикам нужно заботиться только о зависимостях текущего модуля и других вещах.Sea.js
будет обрабатываться автоматически. Для разработчиков модулей это хорошее разделение задач, позволяющее программистам получать больше удовольствия от написания кода. - пройти через
define
Определите модуль, для получения более подробной информации см.SeasJS | Академия гиков.
Пример
Например, для следующегоutil.js
код
var org = {};
org.CoolSite = {};
org.CoolSite.Utils = {};
org.CoolSite.Utils.each = function (arr) {
// 实现代码
};
org.CoolSite.Utils.log = function (str) {
// 实现代码
};
Может быть переписан в SeaJS как
define(function(require, exports) {
exports.each = function (arr) {
// 实现代码
};
exports.log = function (str) {
// 实现代码
};
});
пройти черезexports
интерфейс может быть предоставлен извне. пройти черезrequire('./util.js')
вы можете получитьutil.js
прошедшийexports
открытый интерфейс. здесьrequire
можно считатьSea.js
Ключевое слово синтаксиса, добавленное в язык JavaScript,пройти черезrequire
Можно получить интерфейсы, предоставляемые другими модулями.
define(function(require, exports) {
var util = require('./util.js');
exports.init = function() {
// 实现代码
};
});
Разница между SeaJS и RequireJS
Основное различие между ними состоит в том,Время инициализации модуля
-
В AMD (RequireJS) всякий раз, когда модуль используется в качестве зависимости, он загружается и инициализируется. то есть выполнять (зависимые) модули как можно раньше. Это эквивалентно тому, что все требования являются продвинутыми, и порядок выполнения модулей не обязательно на 100% соответствует порядку написания требований.
-
В CMD (SeaJS) модуль будет инициализирован только тогда, когда он используется как зависимость и на него ссылаются, в противном случае он будет только загружен. То есть он будет инициализирован только тогда, когда модуль действительно нужно использовать. Порядок загрузки модулей строго соответствует порядку написания require.
С точки зрения спецификаций, AMD проще и строже, и имеет более широкое применение.При активном продвижении RequireJS он почти стал стандартом де-факто асинхронных модулей за рубежом, и основные библиотеки также поддерживают спецификации AMD.
Но с точки зрения SeaJS и CMD они также сделали много хорошего: 1. Относительно естественный стиль объявления зависимостей 2. Небольшая и красивая внутренняя реализация 3. Продуманный дизайн периферийных функций 4. Лучшая поддержка китайского сообщества.
UMD
-
UMD
=Universal Module Definition
, общее определение модуля.UMD
даAMD
а такжеCommonJS
смешивание.
AMD
Модули разрабатываются в первую очередь для браузера, загружая модули асинхронно.CommonJS
Модули разрабатываются по принципу server-first, выбирая синхронную загрузку. Его модули представляют собой развернутые модули. Это заставляет людей придумывать еще одну, более общую схему.UMD
(Универсальное определение модуля) для достижения кросс-платформенных решений.
-
UMD
Сначала определите, поддерживать лиNode.js
модуль (exports
) существует, если он существует, используйтеNode.js
модульный режим. Определите, следует ли поддерживатьAMD
(define
существует), если он существует, используйтеAMD
способ загрузки модуля.
(function (window, factory) {
if (typeof exports === 'object') {
module.exports = factory();
} else if (typeof define === 'function' && define.amd) {
define(factory);
} else {
window.eventUtil = factory();
}
})(this, function () {
//module ...
});
Модули ES6
Различия между модулями ES6 и CommonJS
- Модули ES6 выводят ссылку на значение, а выходной интерфейс динамически привязывается, в то время как
CommonJS
Результатом является копия значения. CommonJS
Модули загружаются во время выполнения, модули ES6 представляют собой скомпилированные интерфейсы.
Копия выходного значения CommonJS
Вывод модулей CommonJS — это копия значения (аналогично присвоению типов-примитивов и ссылочных типов). Для примитивных типов после экспорта изменения внутри модуля не влияют на это значение. Для ссылочных типов эффект аналогичен операции присваивания ссылочных типов.
// lib.js
var counter = 3;
var obj = {
name: 'David'
};
function changeValue() {
counter++;
obj.name = 'Peter';
};
module.exports = {
counter: counter,
obj: obj,
changeValue: changeValue,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
console.log(mod.obj.name); // 'David'
mod.changeValue();
console.log(mod.counter); // 3
console.log(mod.obj.name); // 'Peter'
// Or
console.log(require('./lib').counter); // 3
console.log(require('./lib').obj.name); // 'Peter'
-
counter
Это значение базового типа, и изменение внутреннего значения модуля не влияет на изменение выходного значения. -
obj
Это значение ссылочного типа, и изменение внутреннего значения модуля влияет на изменение выходного значения. - Вышеупомянутые два отличия аналогичны операциям присваивания типов-примитивов и ссылочных типов.
Вы также можете использовать функцию значения (getter
),Будуcounter
При преобразовании в значение ссылочного типа эффект следующий.
Внутри класса вы можете использовать
get
а такжеset
Ключевое слово, установите функцию хранения и функцию значения для свойства и перехватите поведение доступа к свойству. ——класс | Жуан Ифэн
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 4
Ссылки на выходные значения ES6
Модули ES6 являются значениями в динамически связанных модулях, и вывод достоин ссылки.Исходное значение изменилось,import
Загруженное значение также изменится соответствующим образом.
ES6
Механизм работы модуля иCommonJS
Разные. Когда движок JS статически анализирует скрипт, он встречает команду загрузки модуляimport
, создается ссылка только для чтения. Когда скрипт действительно выполняется, в соответствии с этой ссылкой только для чтения перейдите к загруженному модулю, чтобы получить значение.В модулях ES6 исходное значение изменилось,import
Загруженное значение также изменится соответствующим образом. Таким образом, модули ES6 динамически ссылаются, а значения не кэшируются.. ——Загрузка реализации модуля ES6 | Руан Ифэн
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
Среда выполнения CommonJS загружает статическую компиляцию ES6
CommonJS
Модули загружаются во время выполнения, модули ES6 представляют собой скомпилированные интерфейсы.
Это потому что,CommonJS
объект загружен(которыйmodule.exports
свойство), объект не будет создан, пока скрипт не завершит работу. а такжеES6
Модули не являются объектами, его внешний интерфейс — это просто статическое определение, которое будет сгенерировано на этапе статического анализа кода.
Модуль ES6 представляет собой интерфейс вывода времени компиляции, поэтому он имеет следующие две характеристики:
-
import
Команды будут статически анализироваться движком JS и выполняться перед другим содержимым модуля. -
export
Команда будет иметь эффект продвижения объявления переменной
импорт имеет приоритет
Импортировать в любом месте файлаimport
Модули будут перемещены в начало файла
// a.js
console.log('a.js')
import { foo } from './b';
// b.js
export let foo = 1;
console.log('b.js 先执行');
// 执行结果:
// b.js 先执行
// a.js
несмотря на то чтоa
модульimport
Введено послеconsole.log('a')
, но он статически анализируется движком JS и упоминается в начале выполнения модуля, что превосходит выполнение других частей модуля.
Переменная команды экспорта улучшает эффект
из-заimport
а такжеexport
выполняется статически, поэтомуimport
а такжеexport
Иметь переменный эффект подъема. которыйimport
а такжеexport
Расположение команды в модуле не влияет на вывод программы.
// a.js
import { foo } from './b';
console.log('a.js');
export const bar = 1;
export const bar2 = () => {
console.log('bar2');
}
export function bar3() {
console.log('bar3');
}
// b.js
export let foo = 1;
import * as a from './a';
console.log(a);
// 执行结果:
// { bar: undefined, bar2: undefined, bar3: [Function: bar3] }
// a.js
a
ссылки на модулиb
модуль,b
Модуль также ссылаетсяa
модуль,export
Объявленные переменные также имеют приоритет над выполнением остальной части модуля. Но конкретное присвоение переменной должно дождаться выполнения соответствующего кода.
Модули ES6 такие же, как CommonJS.
Модули не выполняются повторно
Когда один и тот же модуль импортируется повторно, модуль будет выполнен только один раз.
круговая зависимость
Круговые зависимости модуля CommonJS
Важной особенностью модулей CommonJS является то, что они выполняются при загрузке, то есть код скрипта выполняется вrequire
, все будет выполнено. Как только модуль "циклически загружается",Будет выведена только та часть, которая была выполнена, а часть, которая не была выполнена, не будет выведена.
Demo 1
//a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');
В приведенном выше кодеa.js
Сначала скрипт выводитdone
переменная, а затем загрузите другой файл сценарияb.js
. Обратите внимание, что в это времяa.js
Код останавливается здесь, подождитеb.js
После завершения выполнения переходите к следующему выполнению.
посмотри сноваb.js
код.
//b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');
В приведенном выше кодеb.js
Выполните вторую строку, она загрузитсяa.js
, в это время происходит "циклическая загрузка". система будетa.js
соответствующий объект модуляexports
значение атрибута, но посколькуa.js
еще не закончено, изexports
Свойства могут получить только ту часть, которая была выполнена, а не окончательное значение.
a.js
Часть, которая была выполнена, представляет собой только одну строку.
exports.done = false;
Следовательно, дляb.js
, начинается сa.js
введите только одну переменнуюdone
, значениеfalse
.
Потом,b.js
Затем спуститесь вниз и выполните, дождитесь завершения всех исполнений, а затем верните право выполнения наa.js
. тогда,a.js
Затем продолжайте выполнение, пока выполнение не будет завершено. мы пишем сценарийmain.js
, проверьте процесс.
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);
воплощать в жизньmain.js
, результат операции следующий.
$ node main.js
在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true
Приведенный выше код подтверждает 2 пункта
- существует
b.js
среди,a.js
Не выполняется, выполняется только первая строка -
main.js
Когда вторая строка будет выполнена, она больше не будет выполнятьсяb.js
, но вывод кэшируетсяb.js
, что является его четвертой строкой.
exports.done = true;
Вкратце,Ввод CommonJS — это копия выходного значения, а не ссылка.
Кроме того, когда модули CommonJS сталкиваются с циклической загрузкой, они возвращают значение текущей выполняемой части, а не значение после выполнения всего кода, между ними могут быть различия. Поэтому вы должны быть очень осторожны при вводе переменных.
var a = require('a'); // 安全的写法 导入整体,保证module已经执行完成
var foo = require('a').foo; // 危险的写法
exports.good = function (arg) {
return a.foo('good', arg); // 使用的是 a.foo 的最新值
};
exports.bad = function (arg) {
return foo('bad', arg); // 使用的是一个部分加载时的值
};
В приведенном выше коде, если происходит циклическая загрузка,require('a').foo
Значение, вероятно, будет перезаписано позже, используйтеrequire('a')
было бы более безопасно.
Demo 2
// a.js
console.log('a starting');
exports.done = false;
const b = require('./b');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');
// b.js
console.log('b starting');
exports.done = false;
const a = require('./a');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');
// node a.js
// 执行结果:
// a starting
// b starting
// in b, a.done = false
// b done
// in a, b.done = true
// a done
Из приведенного выше процесса выполнения видно, что в спецификации CommonJS при встречеrequire()
оператор будет выполненrequire
код в модуле,И кешировать результат выполнения.При следующей загрузке выполнение повторяться не будет, а будет взят напрямую кешированный результат. Из-за этого нет бесконечного цикла вызовов, когда есть циклическая зависимость.
Круговые зависимости модуля ES6
Как и модули CommonJS, ES6 больше не будет выполнять повторно загруженные модули, а благодаря функции динамической привязки вывода ES6 ES6 может гарантировать, что ES6 сможет получать текущие последние значения других модулей в любое время.
динамический импорт()
Модули ES6 статически анализируются во время компиляции,Выполняется по сравнению с другим содержимым в модуле, поэтому мы не можем написать код, подобный следующему
if(some condition) {
import a from './a';
}else {
import b from './b';
}
// or
import a from (str + 'b');
Из-за статического анализа во время компиляции мы не можем использовать условные операторы или модули сращивания строк, потому что это результаты, которые необходимо определить во время выполнения.Модули ES6 не разрешены, поэтому динамический импортimport()
возник.
import()
Позволяет динамически импортировать модули ES6 во время выполнения, подумайте об этом, вы также можете подумать об этомrequire.ensure
Это синтаксис, но их использование совершенно различно.
require.ensure
по-видимомуwebpack
Продукт , потому что браузеру нужен асинхронный механизм, который можно использовать для асинхронной загрузки модулей, тем самым уменьшая размер файла начальной загрузки, поэтому, если он находится на стороне сервера,require.ensure
Это бесполезно, потому что на стороне сервера нет асинхронной загрузки модулей, а модули могут загружаться синхронно для соответствия сценариям использования. Модули CommonJS могут подтверждать загрузку модулей во время выполнения.
а такжеimport()
Это другое, в основном это связано с тем, что модуль ES6 не может определить ссылочное отношение модуля во время выполнения, поэтому его необходимо ввестиimport()
.
Посмотрим, как он используется
- Динамический
import()
предоставить на основеPromise
изAPI
- Динамический
import()
Может использоваться в любом месте скриптаimport()
принимает строковые литералы, спецификаторы могут быть созданы по мере необходимости
// a.js
const str = './b';
const flag = true;
if(flag) {
import('./b').then(({foo}) => {
console.log(foo);
})
}
import(str).then(({foo}) => {
console.log(foo);
})
// b.js
export const foo = 'foo';
// babel-node a.js
// 执行结果
// foo
// foo
Конечно, если браузерimport()
станет более широко использоваться, например, асинхронная загрузка модулей по требованию, а затемrequire.ensure
Функция похожа.
потому что он основан наPromise
, поэтому, если вы хотите загрузить несколько модулей одновременно, вы можетеPromise.all
Делайте параллельную асинхронную загрузку.
Promise.all([
import('./a.js'),
import('./b.js'),
import('./c.js'),
]).then(([a, {default: b}, {c}]) => {
console.log('a.js is loaded dynamically');
console.log('b.js is loaded dynamically');
console.log('c.js is loaded dynamically');
});
а такжеPromise.race
метод, который проверяет, какойPromise
быть первымresolved
илиreject
. мы можем использоватьimport()
Чтобы проверить, какойCDN
Быстрее:
const CDNs = [
{
name: 'jQuery.com',
url: 'https://code.jquery.com/jquery-3.1.1.min.js'
},
{
name: 'googleapis.com',
url: 'https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js'
}
];
console.log(`------`);
console.log(`jQuery is: ${window.jQuery}`);
Promise.race([
import(CDNs[0].url).then(()=>console.log(CDNs[0].name, 'loaded')),
import(CDNs[1].url).then(()=>console.log(CDNs[1].name, 'loaded'))
]).then(()=> {
console.log(`jQuery version: ${window.jQuery.fn.jquery}`);
});
Конечно, если вы считаете, что это недостаточно элегантно, вы также можете комбинироватьasync/await
синтаксический сахар для использования.
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
динамичныйimport()
Дает нам дополнительную функциональность для асинхронного использования модулей ES.
Динамическая или условная загрузка их в зависимости от наших потребностей позволяет нам быстрее и качественнее создавать больше полезных приложений.
Загрузка 3 модулей в webpack | Синтаксис
Webpack допускает различные типы модулей, но底层
Должна использоваться одна и та же реализация. Все модули можно запускать прямо из коробки.
- Модули ES6
import MyModule from './MyModule.js';
- CommonJS(Require)
var MyModule = require('./MyModule.js');
- AMD
define(['./MyModule.js'], function (MyModule) {
});