Модульность JS — Сравнение модулей CommonJS AMD CMD UMD ES6

JavaScript

Модульная разработка

преимущество

  • В модульной разработке файл обычно представляет собой модуль, который имеет свою собственную область видимости, предоставляет только определенные переменные и функции и может быть загружен по запросу.
  • Зависимости загружаются автоматически и загружаются по запросу.
  • Повысьте уровень повторного использования кода, упростите управление кодом и сделайте управление кодом более понятным и стандартизированным.
  • Сокращены конфликты имен и устранены глобальные переменные.
  • В настоящее время популярные спецификации модульности 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-cmd-nodek

С точки зрения спецификаций, 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) {
});

использованная литература