предисловие
На ранней стадии разработки JavaScript нужно было просто реализовать простую логику взаимодействия со страницей, всего несколько слов; сейчас производительность ЦП и браузера значительно улучшена, и многие логики страницы были перенесены на клиент (проверка форм и т. д.). .), с развитием веб 2.0 С наступлением эпохи широко используется технология Ajax, jQuery и другие интерфейсные библиотеки появляются одна за другой, а интерфейсный код расширяется день ото дня. Со временем JS рассмотрит возможность использования модульных спецификаций для управления. Содержание этой статьи в основном включает в себя понимание модуляризации, почему модульность необходима, преимущества и недостатки модульности и спецификации модульности, а также представляет наиболее популярные спецификации CommonJS, AMD, ES6 и CMD, находящиеся в разработке. В этой статье делается попытка представить эти скучные концепции в простом для понимания стиле с точки зрения Xiaobai.Я надеюсь, что после прочтения у вас появится новое понимание и понимание модульного программирования!
Рекомендуется скачать исходный код этой статьи и выбить его самостоятельно, ткните посильнееЛичный блог GitHub (полная работа)
Понимание модульности
1. Что такое модуль?
- Инкапсулировать сложную программу на несколько блоков (файлов) в соответствии с определенными правилами (спецификациями) и объединить их вместе
- Внутренний блок данных реализован собственными средствами, просто некоторые интерфейсы (методы) выставлены на внешнюю связь с другими внешними модулями.
2. Процесс модульной эволюции
-
Режим глобальной функции: инкапсулируйте разные функции в разные глобальные функции.
- Кодирование: инкапсулируйте разные функции в разные глобальные функции.
- Проблема: загрязняет глобальное пространство имен, легко вызывая конфликты имен или небезопасность данных, и нет прямой связи между членами модуля.
function m1(){
//...
}
function m2(){
//...
}
-
режим пространства имен: простая инкапсуляция объектов
- Функция: сокращение глобальных переменных и разрешение конфликтов имен.
- Проблема: данные не безопасны (извне может напрямую изменить данные внутри модуля)
let myModule = {
data: 'www.baidu.com',
foo() {
console.log(`foo() ${this.data}`)
},
bar() {
console.log(`bar() ${this.data}`)
}
}
myModule.data = 'other data' //能直接修改模块内部的数据
myModule.foo() // foo() other data
Этот способ записи раскрывает все элементы модуля, а внутреннее состояние может быть переписано извне.
-
Режим IIFE: самовызов анонимной функции (закрытие)
- Функция: данные являются частными, и снаружи могут работать только открытые методы.
- Кодирование: инкапсулируйте данные и поведение в функцию и раскрывайте интерфейс, добавляя свойства в окно.
- Вопрос: Что делать, если текущий модуль зависит от другого модуля?
// index.html文件
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
myModule.foo()
myModule.bar()
console.log(myModule.data) //undefined 不能访问模块内部数据
myModule.data = 'xxxx' //不是修改的模块内部的data
myModule.foo() //没有改变
</script>
// module.js文件
(function(window) {
let data = 'www.baidu.com'
//操作数据的函数
function foo() {
//用于暴露有函数
console.log(`foo() ${data}`)
}
function bar() {
//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() {
//内部私有的函数
console.log('otherFun()')
}
//暴露行为
window.myModule = { foo, bar } //ES6写法
})(window)
Окончательный результат:
- Расширение режима IIFE: знакомство с зависимостями
Это краеугольный камень реализации современных модулей.
// module.js文件
(function(window, $) {
let data = 'www.baidu.com'
//操作数据的函数
function foo() {
//用于暴露有函数
console.log(`foo() ${data}`)
$('body').css('background', 'red')
}
function bar() {
//用于暴露有函数
console.log(`bar() ${data}`)
otherFun() //内部调用
}
function otherFun() {
//内部私有的函数
console.log('otherFun()')
}
//暴露行为
window.myModule = { foo, bar }
})(window, jQuery)
// index.html文件
<!-- 引入的js必须有一定顺序 -->
<script type="text/javascript" src="jquery-1.10.1.js"></script>
<script type="text/javascript" src="module.js"></script>
<script type="text/javascript">
myModule.foo()
</script>
В приведенном выше примере используется метод jquery для изменения цвета фона страницы на красный, поэтому сначала необходимо ввести библиотеку jQuery, и эта библиотека передается в качестве параметра.Помимо обеспечения независимости модулей, это также делает очевидными зависимости между модулями..
3. Преимущества модульности
- Избегайте конфликтов имен (уменьшайте загрязнение пространства имен)
- Лучшее разделение, загрузка по запросу
- более высокая возможность повторного использования
- Высокая ремонтопригодность
4. Введите несколько<script>
Проблема возникает после
- Слишком много запросов
Во-первых, мы должны полагаться на несколько модулей, поэтому он будет отправлять несколько запросов, что приведет к избыточным запросам.
- неоднозначность зависимости
Мы не знаем, каковы их конкретные зависимости, а это означает, что легко вызвать ошибки в порядке загрузки из-за непонимания зависимостей между ними.
- сложно поддерживать
Вышеуказанные две причины затрудняют его обслуживание и могут вызвать серьезные проблемы в проекте. Конечно, модульность имеет много преимуществ, но на странице необходимо разместить несколько файлов js, и возникнут описанные выше проблемы. И эти проблемы могут быть решены с помощью модульных спецификаций.Наиболее популярные в разработке спецификации commonjs, AMD, ES6, CMD представлены ниже.
2. Модульная спецификация
1.CommonJS
(1 Обзор
Приложения Node состоят из модулей, использующих спецификацию модулей CommonJS. Каждый файл является модулем и имеет свою собственную область видимости. Переменные, функции и классы, определенные в файле, являются частными и невидимыми для других файлов.На стороне сервера модули загружаются синхронно во время выполнения, на стороне браузера модули должны быть скомпилированы и упакованы заранее.
(2) Особенности
- Весь код выполняется в области модуля и не загрязняет глобальную область.
- Модуль можно загружать несколько раз, но он будет запущен только один раз при первой загрузке, а затем текущий результат будет кэширован, а при последующей загрузке кэшированный результат будет прочитан напрямую. Чтобы модуль снова заработал, необходимо очистить кеш.
- Порядок загрузки модулей, порядок их появления в коде.
(3) Базовая грамматика
- Выставить модуль:
module.exports = value
илиexports.xxx = value
- Импортируйте модуль:
require(xxx)
, если это сторонний модуль, xxx — это имя модуля; если это пользовательский модуль, xxx — это путь к файлу модуля
Вот у нас вопрос:Какой модуль предоставляет CommonJS?Спецификация CommonJS предусматривает, что внутри каждого модуля переменная модуля представляет текущий модуль. Эта переменная является объектом, чьим свойством exports (т.е. module.exports) является внешний интерфейс.Загрузка модуля фактически загружает свойство module.exports модуля..
// example.js
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
Приведенный выше код выводит переменную x и функцию addX через module.exports.
var example = require('./example.js');//如果参数字符串以“./”开头,则表示加载的是一个位于相对路径
console.log(example.x); // 5
console.log(example.addX(1)); // 6
Команда require используется для загрузки файлов модулей.Основная функция команды требуется, чтобы прочитать и выполнить файл JavaScript, а затем вернуть объект экспорта модуля. Если указанный модуль не найден, сообщается об ошибке.
(4) Модуль модуля
Механизм загрузки модулей CommonJS заключается в том, что ввод является копией значения, которое выводится. То есть после вывода значения изменения внутри модуля не могут повлиять на это значение.. Это основное отличие от модульности ES6 (описанной ниже), см. следующий пример:
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
Приведенный выше код выводит счетчик внутренней переменной и внутренний метод incCounter, который переопределяет эту переменную.
// main.js
var counter = require('./lib').counter;
var incCounter = require('./lib').incCounter;
console.log(counter); // 3
incCounter();
console.log(counter); // 3
Приведенный выше код показывает, что после вывода счетчика изменения внутри модуля lib.js не повлияют на счетчик.Это связано с тем, что counter является примитивным значением и будет кэшироваться. Если вы не напишите функцию, вы можете получить значение после внутреннего изменения.
(5) Реализация на стороне сервера
① Загрузите и установите node.js
②Создайте структуру проекта
Примечание. При использовании npm init для автоматического создания package.json имя пакета (имя пакета) не может быть китайским и заглавным.
|-modules
|-module1.js
|-module2.js
|-module3.js
|-app.js
|-package.json
{
"name": "commonJS-node",
"version": "1.0.0"
}
③ Загрузите сторонние модули
npm install uniq --save // 用于数组去重
④Определите код модуля
//module1.js
module.exports = {
msg: 'module1',
foo() {
console.log(this.msg)
}
}
//module2.js
module.exports = function() {
console.log('module2')
}
//module3.js
exports.foo = function() {
console.log('foo() module3')
}
exports.arr = [1, 2, 3, 3, 2]
// app.js文件
// 引入第三方库,应该放置在最前面
let uniq = require('uniq')
let module1 = require('./modules/module1')
let module2 = require('./modules/module2')
let module3 = require('./modules/module3')
module1.foo() //module1
module2() //module2
module3.foo() //foo() module3
console.log(uniq(module3.arr)) //[ 1, 2, 3 ]
⑤Запустите app.js через узел
ввод командной строкиnode app.js
, запустите JS-файл
(6) Реализация на стороне браузера (с Browserify)
① Создайте структуру проекта
|-js
|-dist //打包生成文件的目录
|-src //源码所在的目录
|-module1.js
|-module2.js
|-module3.js
|-app.js //应用主源文件
|-index.html //运行于浏览器上
|-package.json
{
"name": "browserify-test",
"version": "1.0.0"
}
②Скачать в браузере
- Глобально: npm install browserify -g
- Локально: npm install browserify --save-dev
③ Определите код модуля (такой же, как на стороне сервера)
Уведомление:index.html
Чтобы запустить файл в браузере, вам нужно использовать browserify дляapp.js
Файл запакован и скомпилирован, если прямо вindex.html
представлятьapp.js
сообщит об ошибке!
④Упаковка и обработка js
запустить в корневом каталогеbrowserify js/src/app.js -o js/dist/bundle.js
⑤ Введение в использование страницы
Представлено в файле index.html.<script type="text/javascript" src="js/dist/bundle.js"></script>
2.AMD
Спецификация CommonJS загружает модули синхронно, то есть только после завершения загрузки можно выполнять последующие операции. Спецификация AMD заключается в том, чтобы загружать модули асинхронно, позволяя указывать функции обратного вызова. Поскольку Node.js в основном используется для серверного программирования, файлы модулей, как правило, уже существуют на локальном жестком диске, поэтому загрузка происходит быстрее, а метод асинхронной загрузки не рассматривается, поэтому спецификация CommonJS более применима. но,Если это среда браузера, для загрузки модулей со стороны сервера необходимо использовать асинхронный режим, поэтому сторона браузера обычно использует спецификацию AMD.. Кроме того, спецификация AMD была реализована раньше, чем спецификация CommonJS на стороне браузера.
(1) Базовый синтаксис спецификации AMD
Определить открытые модули:
//定义没有依赖的模块
define(function(){
return 模块
})
//定义有依赖的模块
define(['module1', 'module2'], function(m1, m2){
return 模块
})
модуль импорта:
require(['module1', 'module2'], function(m1, m2){
使用m1/m2
})
(2) Не использовать спецификацию AMD и использовать require.js
Путем сравнения двух методов реализации проиллюстрированы преимущества использования спецификации AMD.
- Не использовать спецификации AMD
// dataService.js文件
(function (window) {
let msg = 'www.baidu.com'
function getMsg() {
return msg.toUpperCase()
}
window.dataService = {getMsg}
})(window)
// alerter.js文件
(function (window, dataService) {
let name = 'Tom'
function showMsg() {
alert(dataService.getMsg() + ', ' + name)
}
window.alerter = {showMsg}
})(window, dataService)
// main.js文件
(function (alerter) {
alerter.showMsg()
})(alerter)
// index.html文件
<div><h1>Modular Demo 1: 未使用AMD(require.js)</h1></div>
<script type="text/javascript" src="js/modules/dataService.js"></script>
<script type="text/javascript" src="js/modules/alerter.js"></script>
<script type="text/javascript" src="js/main.js"></script>
В итоге получил следующий результат:
Недостатки этого метода очевидны:Во-первых, будет отправлено несколько запросов, а во-вторых, порядок вводимых js файлов не может быть неправильным, иначе будет сообщено об ошибке!
- использовать require.js
RequireJS — это библиотека инструментов, которая в основном используется для управления модулями на стороне клиента. Управление модулями соответствует спецификациям AMD,Основная идея RequireJS заключается в том, что код определяется как модуль через метод define, модульная загрузка кода реализуется через метод require. Далее мы представляем шаги реализации спецификации AMD в браузерах:
① Загрузите require.js и импортируйте
- Официальный сайт:
http://www.requirejs.cn/
- github :
https://github.com/requirejs/requirejs
Затем импортируйте в проект require.js: js/libs/require.js
②Создайте структуру проекта
|-js
|-libs
|-require.js
|-modules
|-alerter.js
|-dataService.js
|-main.js
|-index.html
③Определите код модуля require.js
// dataService.js文件
// 定义没有依赖的模块
define(function() {
let msg = 'www.baidu.com'
function getMsg() {
return msg.toUpperCase()
}
return { getMsg } // 暴露模块
})
//alerter.js文件
// 定义有依赖的模块
define(['dataService'], function(dataService) {
let name = 'Tom'
function showMsg() {
alert(dataService.getMsg() + ', ' + name)
}
// 暴露模块
return { showMsg }
})
// main.js文件
(function() {
require.config({
baseUrl: 'js/', //基本路径 出发点在根目录下
paths: {
//映射: 模块标识名: 路径
alerter: './modules/alerter', //此处不能写成alerter.js,会报错
dataService: './modules/dataService'
}
})
require(['alerter'], function(alerter) {
alerter.showMsg()
})
})()
// index.html文件
<!DOCTYPE html>
<html>
<head>
<title>Modular Demo</title>
</head>
<body>
<!-- 引入require.js并指定js主文件的入口 -->
<script data-main="js/main" src="js/libs/require.js"></script>
</body>
</html>
④На странице представлен модуль require.js:
Представлено в index.html<script data-main="js/main" src="js/libs/require.js"></script>
Кроме того, как представить сторонние библиотеки в проекте? Просто основа небольших изменений в приведенном выше коде:
// alerter.js文件
define(['dataService', 'jquery'], function(dataService, $) {
let name = 'Tom'
function showMsg() {
alert(dataService.getMsg() + ', ' + name)
}
$('body').css('background', 'green')
// 暴露模块
return { showMsg }
})
// main.js文件
(function() {
require.config({
baseUrl: 'js/', //基本路径 出发点在根目录下
paths: {
//自定义模块
alerter: './modules/alerter', //此处不能写成alerter.js,会报错
dataService: './modules/dataService',
// 第三方库模块
jquery: './libs/jquery-1.10.1' //注意:写成jQuery会报错
}
})
require(['alerter'], function(alerter) {
alerter.showMsg()
})
})()
В приведенном выше примере сторонняя библиотека jQuery представлена в файле alerter.js, а файл main.js также имеет соответствующую конфигурацию пути.резюме: Сравнивая их, можно сделать вывод, чтоМетод, определенный модулем AMD, очень ясен, не загрязняет глобальную среду и может четко отображать зависимости.. Режим AMD может использоваться в среде браузера и позволяет загружать модули асинхронно или динамически по запросу.
3.CMD
Спецификация CMD специально используется на стороне браузера.Загрузка модулей происходит асинхронно, и модули загружаются и выполняются, когда они используются. Спецификация CMD сочетает в себе функции спецификаций CommonJS и AMD. В Sea.js все модули JavaScript соответствуют спецификации определения модуля CMD.
(1) Базовый синтаксис спецификации CMD
Определите открытый модуль:
//定义没有依赖的模块
define(function(require, exports, module){
exports.xxx = value
module.exports = value
})
//定义有依赖的模块
define(function(require, exports, module){
//引入依赖模块(同步)
var module2 = require('./module2')
//引入依赖模块(异步)
require.async('./module3', function (m3) {
})
//暴露模块
exports.xxx = value
})
Импорт с помощью модулей:
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
(2) простое руководство по использованию sea.js
① Загрузите sea.js и импортируйте
- Официальный сайт:seajs.org/
- github : github.com/seajs/seajs
Затем импортируйте в проект sea.js: js/libs/sea.js
②Создайте структуру проекта
|-js
|-libs
|-sea.js
|-modules
|-module1.js
|-module2.js
|-module3.js
|-module4.js
|-main.js
|-index.html
③Определить код модуля sea.js
// module1.js文件
define(function (require, exports, module) {
//内部变量数据
var data = 'atguigu.com'
//内部函数
function show() {
console.log('module1 show() ' + data)
}
//向外暴露
exports.show = show
})
// module2.js文件
define(function (require, exports, module) {
module.exports = {
msg: 'I Will Back'
}
})
// module3.js文件
define(function(require, exports, module) {
const API_KEY = 'abc123'
exports.API_KEY = API_KEY
})
// module4.js文件
define(function (require, exports, module) {
//引入依赖模块(同步)
var module2 = require('./module2')
function show() {
console.log('module4 show() ' + module2.msg)
}
exports.show = show
//引入依赖模块(异步)
require.async('./module3', function (m3) {
console.log('异步引入依赖模块3 ' + m3.API_KEY)
})
})
// main.js文件
define(function (require) {
var m1 = require('./module1')
var m4 = require('./module4')
m1.show()
m4.show()
})
④Ввести в index.html
<script type="text/javascript" src="js/libs/sea.js"></script>
<script type="text/javascript">
seajs.use('./js/modules/main')
</script>
Окончательный результат выглядит следующим образом:
4. Модульность ES6
Идея дизайна модулей ES6 состоит в том, чтобы быть как можно более статичными, чтобы зависимости модулей, а также входные и выходные переменные можно было определить во время компиляции. Модули CommonJS и AMD могут определять эти вещи только во время выполнения. Например, модули CommonJS являются объектами, и свойства объекта необходимо искать при вводе.
(1) Модульный синтаксис ES6
Команда экспорта используется для указания внешнего интерфейса модуля, а команда импорта используется для импорта функций, предоставляемых другими модулями.
/** 定义模块 math.js **/
var basicNum = 0;
var add = function (a, b) {
return a + b;
};
export { basicNum, add };
/** 引用模块 **/
import { basicNum, add } from './math';
function test(ele) {
ele.textContent = add(99 + basicNum);
}
Как показано в приведенном выше примере, при использовании команды импорта пользователю необходимо знать имя загружаемой переменной или функции, иначе она не может быть загружена. Чтобы пользователям было проще загружать модули, не читая документацию, используется команда экспорта по умолчанию для указания вывода по умолчанию для модуля.
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'
Модуль выводится по умолчанию.Когда другие модули загружают модуль, команда импорта может указать любое имя для анонимной функции.
(2) Различия между модулями ES6 и модулями CommonJS
У них есть два основных отличия:
① Модули CommonJS выводят копию значения, а модули ES6 выводят ссылку на значение..
② Модуль CommonJS загружается во время выполнения, а модуль ES6 является выходным интерфейсом во время компиляции..
Второе отличие заключается в том, что CommonJS загружает объект (свойство module.exports), который не создается до тех пор, пока скрипт не завершит работу. Модуль ES6 не является объектом, его внешний интерфейс — это просто статическое определение, которое будет сгенерировано на этапе статического анализа кода.
Давайте сосредоточимся на объяснении первого отличия, возьмем пример механизма загрузки модуля CommonJS выше:
// 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
Модули ES6 работают иначе, чем CommonJS.Модули ES6 динамически ссылаются и не кэшируют значения Переменные в модулях привязаны к модулю, в котором они расположены..
(3) Руководство по ES6-Babel-Browserify
В одном предложении:Используйте Babel для компиляции кода ES6 в ES5 и используйте Browserify для компиляции и упаковки js..
① Определите файл package.json
{
"name" : "es6-babel-browserify",
"version" : "1.0.0"
}
②Установите babel-cli, babel-preset-es2015 и установите браузер.
- npm install babel-cli browserify -g
- npm install babel-preset-es2015 --save-dev
- предустановленный пресет (упакуйте все плагины, конвертирующие es6 в es5)
③Определите файл .babelrc
{
"presets": ["es2015"]
}
④Определите код модуля
//module1.js文件
// 分别暴露
export function foo() {
console.log('foo() module1')
}
export function bar() {
console.log('bar() module1')
}
//module2.js文件
// 统一暴露
function fun1() {
console.log('fun1() module2')
}
function fun2() {
console.log('fun2() module2')
}
export { fun1, fun2 }
//module3.js文件
// 默认暴露 可以暴露任意数据类项,暴露什么数据,接收到就是什么数据
export default () => {
console.log('默认暴露')
}
// app.js文件
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
foo()
bar()
fun1()
fun2()
module3()
⑤ Скомпилируйте и импортируйте в index.html
- Скомпилируйте код ES6 в ES5, используя Babel (но включая синтаксис CommonJS):
babel js/src -d js/lib
- Скомпилируйте js с помощью Browserify:
browserify js/lib/app.js -o js/lib/bundle.js
Затем введите его в файл index.html.
<script type="text/javascript" src="js/lib/bundle.js"></script>
В итоге получил следующий результат:
Кроме того, как внедрить сторонние библиотеки (например, jQuery)??
Сначала установите зависимостиnpm install jquery@1
Затем импортируйте его в файл app.js.
//app.js文件
import { foo, bar } from './module1'
import { fun1, fun2 } from './module2'
import module3 from './module3'
import $ from 'jquery'
foo()
bar()
fun1()
fun2()
module3()
$('body').css('background', 'green')
3. Резюме
- Спецификация CommonJS в основном используется для программирования на стороне сервера, модули загружаются синхронно, что не подходит для среды браузера, поскольку синхронность означает блокировку загрузки, ресурсы браузера загружаются асинхронно, поэтому существует решение AMD CMD.
- Спецификация AMD загружает модули асинхронно в среде браузера, и несколько модулей могут загружаться параллельно. Однако стоимость разработки спецификации AMD высока, код трудно читать и писать, а семантика метода определения модуля не является гладкой.
- Спецификация CMD очень похожа на спецификацию AMD, обе используются для браузерного программирования, опираясь на близость, отложенное выполнение и могут быть легко запущены в Node.js. Однако, в зависимости от упаковки СЗМ, логика загрузки модуля смещена.
- На уровне языковых стандартов ES6 реализует модульные функции, и реализация достаточно проста, она может полностью заменить спецификации CommonJS и AMD и стать общим модульным решением для браузеров и серверов..