Как мы все знаем, статус js во фронтенд-разработке. Очень важно хорошо этому научиться.
В следующей статье представлена модульность.
Что такое модульность?
Что такое модульность и модульная разработка?
- На самом деле конечной целью модульной разработки является разделение программы на небольшие структуры.
- Эта структура пишет свой собственный логический код, имеет свою область видимости и не будет влиять на другие структуры.
- Эта структура может экспортировать переменные, функции, объекты и т. д., которые она хочет предоставить своей структуре для использования.
- Вы также можете каким-то образом импортировать переменные, функции, объекты и т. д. в другие структуры.
Вышеупомянутая структура является модулем, а процесс разделения программы разработки в соответствии с этой структурой является процессом модульной разработки.
Модульная история
На заре веб-разработки Брендан Эйх разработал JavaScript только как язык сценариев для простой проверки формы или реализации анимации и т. д. В то время код был еще очень маленьким:
- В настоящее время нам нужно только написать код JavaScript для
- Нет необходимости писать в нескольких файлах.
Но с быстрым развитием интерфейса и JavaScript код JavaScript становится все более и более сложным:
- Появление ajax, разделение front-end и back-end разработки, означает, что после того, как back-end вернет данные, нам нужно отрисовать front-end страницу через JavaScript.
- С появлением SPA внешние страницы стали более сложными: ряд сложных требований, включая внешнюю маршрутизацию, управление состоянием и т. д., необходимо реализовать с помощью JavaScript.
- Включая реализацию Node, написание сложных серверных программ на JavaScript, отсутствие модульности — фатальный недостаток.
Поэтому модульность уже является очень острой потребностью в JavaScript. Так ES6 (2015) запустил собственное модульное решение.
До этого для поддержки модуляризации в JavaScript появилось множество различных спецификаций модульности: AMD, CMD, CommonJS и т. д.
Нет проблем с модульностью
- Например, конфликты имен.
Вышеупомянутая проблема решается выражением немедленного вызова функции (IIFE). Поскольку функции имеют свою собственную область видимости, конфликтов имен между разными файлами не будет.
// a.js
var moduleA = (function() {
var name = "llm"
var age = 22
var isFlag = true
return {
name: name,
isFlag: isFlag
}
})()
// b.js
var moduleB = (function() {
var name = "zh"
var isFlag = false
return {
name: name,
isFlag: isFlag
}
})()
// 使用
moduleA.name
moduleB.name
Однако мы фактически принесли новую проблему:
- Я должен помнить имя возвращаемого объекта в каждом модуле, чтобы правильно использовать его в других модулях.
- Код сложно писать, и код в каждом файле должен быть написан в анонимной функции.
- В отсутствие надлежащей спецификации все и каждая компания может называть что-то произвольно или даже иметь одно и то же имя модуля.
Поэтому мы обнаружим, что, хотя модульность достигнута, наша реализация слишком проста и не имеет спецификации.
Нам нужно сформулировать некую спецификацию, чтобы заставить всех писать модульный код в соответствии с этой спецификацией. Эта спецификация должна включать основную функциональность: сам модуль может экспортировать открытые свойства, а модуль может импортировать необходимые ему свойства. Чтобы решить вышеупомянутые проблемы, сообщество JavaScript разработало ряд полезных спецификаций, а затем мы изучим некоторые репрезентативные спецификации.
Спецификация CommonJS и узел
Нам необходимо знать, что CommonJS — это спецификация. Первоначально она была предложена для использования вне браузера и в то время называлась ServerJS. Позже, чтобы отразить ее широту, она была изменена на CommonJS, которую мы также будем называть CJS для короткая.
- Node — репрезентативная реализация CommonJS на стороне сервера.
- Browserify — это реализация CommonJS в браузере.
- Сборщик веб-пакетов имеет поддержку и преобразования для CommonJS.
Таким образом, CommonJS поддерживается и внедряется в Node, что позволяет нам выполнять модульную разработку во время разработки Node.
- Каждый файл js в Node — это отдельный модуль.
- Этот модуль включает основные переменные спецификации CommonJS: exports, module.exports и require.
- Мы можем использовать эти переменные для облегчения модульной разработки.
Ранее мы упоминали, что ядром модульности является экспорт и импорт, реализованный в Node:
- exports и module.exports могут отвечать за экспорт содержимого модуля.
- Функция require может помочь нам импортировать контент из других модулей (пользовательские модули, системные модули, модули сторонних библиотек).
Модульность Node.js
CommonJS поддерживается и внедряется в Node, так что мы можем легко выполнять модульную разработку в процессе разработки узла:
- Каждый файл js в Node — это отдельный модуль.
- Этот модуль включает основные переменные спецификации CommonJS:
exports、module.exports、require. -
exportsа такжеmodule.exportsМожет отвечать за экспорт контента в модуль. -
requireФункции могут помочь нам импортировать контент из других модулей (пользовательские модули, системные модули, модули сторонних библиотек).
Ниже мы представимexports、module.exports、requireиспользование.
- exports — это объект, мы можем добавить к этому объекту много свойств, и добавленные свойства будут экспортированы.
- Мы также можем экспортировать объект напрямую через module.exports.
- мы проходим
require()Функция импортирует файл. И этот файл экспортирует переменные.
Ниже приводится подробное введение вmodule.exports.
- В CommonJS нет понятия module.exports.
- Однако, чтобы реализовать экспорт модулей, в Node используется класс Module, и каждый модуль является экземпляром Module, то есть модулем.
- Так что на самом деле для экспорта в Node используется вовсе не экспорт, а module.exports.
- Потому что модуль является реальным реализатором экспорта.
- и будет внутренне
exportsназначить наmodule.exports.
Импорт и экспорт этого метода имеет следующие характеристики:
- Все файлы в Node запускаются внутри функции. можно распечатать по
console.log(arguments.callee + "")проверять.
- Импорт и экспорт является ссылкой на значение.Если экспорт является значением базового типа данных, то файл экспорта изменяет значение, а затем значение переменной в файле импорта не изменяется.
// a.js
const obj = require("./b.js")
console.log(obj)
setTimeout(() => {
obj.name = "llm"
}, 1000)
// b.js
const info = {
name: "zh",
age: 22,
foo: function() {
console.log("foo函数~")
}
}
setTimeout(() => {
console.log(info.name) // llm
}, 2000)
module.exports = info
- он через требованиефункцияимпортировать, только ввыполнить js-кодзнать зависимости модулей.
- Код выполняется синхронно.
- Модули, импортированные несколько раз, будут загружены только один раз. Внутри каждого модуля будет загружено, чтобы определить, был ли он загружен.
- Когда вводится цикл кода, модуль загружается в глубину. Потом сначала в ширину.
Ниже приводится подробное введение вrequireИмпорт сведений о
Теперь мы знаем, что require — это функция, которая помогает нам импортировать объекты, экспортированные в файл (модуль).
Итак, каковы правила поиска для require?
Подробные правила поиска смотрите здесь
Здесь я резюмирую наиболее распространенные правила поиска: формат импорта следующий: require(X)
- X — это основной модуль Node, такой как path, http.
- Вернитесь прямо к основному модулю и прекратите поиск.
- Х это
./или../или/(корневая директория).-
Шаг 1: Найдите файл X в соответствующем каталоге.
- Если есть имя суффикса, найдите соответствующий файл в соответствии с форматом имени суффикса.
- Если имя суффикса отсутствует, оно будет в следующем порядке:
- Найти файл X напрямую
- Найти файлы X.js
- Найдите файл X.json
- Найдите файл X.node
-
Шаг 2: Если соответствующий файл не найден, используйте X в качестве каталога.
- Найдите файл индекса в каталоге
- Найдите файл X/index.js
- Найдите файл X/index.json.
- Найдите файл X/index.node
- Найдите файл индекса в каталоге
-
Если не найдено, то сообщается об ошибке: не найдено
-
- является X напрямую (без пути), и X не является основным модулем. Затем он будет искать файл node_modules в верхнем каталоге. пока не будет найден корневой каталог. Мы можем распечатать эти каталоги поиска через module.paths. Если ни один из вышеуказанных путей не найден, сообщается об ошибке: не найдено.
Сведения о загрузке модуля
- Когда модуль впервые представлен, код js в модуле будет запущен один раз.
- Когда модуль импортируется несколько раз, он кэшируется и в конечном итоге загружается (запускается) только один раз.
Почему только один раз загрузить и запустить?
Это связано с тем, что каждый объектный модуль модуля имеет атрибут: загружен. Если оно ложно, это означает, что оно не было загружено, а если оно истинно, то оно было загружено.
- Если введены циклы, каков порядок загрузки?
Как показано выше, Node используеталгоритм поиска в глубину: main -> aaa -> ccc -> ddd -> eee -> bbb
Недостатки спецификации CommonJS
-
Модули загрузки CommonJS синхронны:
- Синхронный означает, что содержимое текущего модуля не может быть выполнено до тех пор, пока соответствующий модуль не будет загружен.
- Это не будет проблемой на сервере, потому что файлы js, загружаемые сервером, являются локальными файлами, а скорость загрузки очень высока.
Что, если он был применен к браузеру?
-
Когда браузер загружает файл js, ему необходимо сначала загрузить файл с сервера, а затем загрузить и запустить его.
-
Затем использование синхронного означает, что последующий код js не может нормально работать, даже некоторые простые операции DOM. Поэтому в браузерах мы обычно не используем спецификацию CommonJS. Конечно, использование CommonJS с webpack — это отдельная история. Потому что он превращает наш код в код, который браузер может выполнять напрямую.
Технические характеристики AMD
В первые дни обычно использовались AMD или CMD, чтобы иметь возможность использовать модульность в браузере. Однако, с одной стороны, современные браузеры уже поддерживают ES-модули, а с другой стороны, с помощью таких инструментов, как webpack, можно реализовать преобразование кода CommonJS или ES-модуля. AMD и CMD использовались очень мало, поэтому здесь мы проводим простое пошаговое руководство.
AMD — это в основном модульная спецификация, применяемая к браузерам:
- AMD — это аббревиатура от определения асинхронного модуля. Он использует асинхронную загрузку модулей.
- Мы упоминали, что спецификация определяет только то, как должен быть написан код, и может применяться только при наличии конкретной реализации.
- Наиболее часто используемые библиотеки, реализованные AMD, — это require.js и curl.js.
Использование require.js
- Загрузите исходный код:Github.com/require js / день ...Найдите в нем файл require.js.
- Тег script, определяющий HTML, вводит require.js и определяет файл записи. Функция атрибута data-main заключается в загрузке и выполнении файла после загрузки файла src.
// index.html
<script src="./require.js" data-main="./index.js"></script>
//main.js
require.config({
baseUrl: '', // 默认是main.js的文件夹路径
paths: {
foo: "./foo"
}
})
require(["foo"], function(foo) {
console.log("main:", foo)
})
// foo.js
define(function() {
const name = "zh"
const age = 22
function sum(num1, num2) {
return num1 + num2
}
return {
name,
age,
sum
}
})
Спецификация CMD
Спецификация CMD также является модульной спецификацией, применяемой к браузерам:
- CMD — это аббревиатура от Common Module Definition. Он также использует асинхронную загрузку модулей, но поглощает преимущества CommonJS.
- Наиболее часто используемой библиотекой, реализованной AMD, является SeaJS.
Использование SeaJS
- Загрузите исходный код:GitHub.com/морской JS/морской JS…Найдите sea.js в папке dist.
- Импортируйте sea.js и используйте основной файл записи.
// index.html
<script src="./sea.js"></script>
<script>
seajs.use("./main.js")
</script>
//main.js
define(function(require, exports, module) {
const foo = require("./foo")
console.log("main:", foo)
})
// foo.js
define(function(require, exports, module) {
const name = "zh"
const age = 22
function sum(num1, num2) {
return num1 + num2
}
// exports.name = name
// exports.age = age
module.exports = {
name,
age,
sum
}
});
ES Module
Есть некоторые различия между модулем ES и модульностью CommonJS:
- С одной стороны, он использует ключевые слова import и export для достижения модульности.
- С другой стороны, он использует статический анализ во время компиляции, а также добавляет динамические ссылки.
- export отвечает за экспорт содержимого модуля.
- import отвечает за импорт содержимого из других модулей.
- Принятие модуля ES автоматически примет строгий режим: используйте строгий.
основное использование
// index.html
<script src="./main.js" type="module"></script>
// foo.js
let obj = {
name: "zh",
age: 22
}
export default sum
// main.js
import foo from './foo.js'
console.log(foo)
- Когда html-файл загружает файл ввода, вам нужно указать тип как модуль.
- При открытии html-файла вам нужно открыть локальную службу, а не напрямую открывать и запускать ее в браузере.
- Вы должны быть осторожны с локальным тестированием — если вы загружаете HTML-файл локально (например, файл с путем file://), вы получите ошибки CORS, как того требует безопасность модуля Javascript.
- Вам нужно передать сервер для тестирования.
ключевое слово экспорта
Ключевое слово export экспортирует переменные, функции, классы и т. д. в модуль.
Мы хотим экспортировать все остальное содержимое следующими способами:
- Способ 1. Добавьте ключевое слово экспорта непосредственно перед объявлением инструкции.
export const name = "zh"
export const age = 22
- Способ 2. Поместите все идентификаторы, которые необходимо экспортировать, в конец экспорта.
{}середина.Примечание. {} здесь не является расширенной нотацией литералов объектов ES6, а {} не представляет объект. Итак: экспорт {имя: имя} неверен.
const name = "zh"
const age = 22
function foo() {
console.log("foo function")
}
export {
name,
age,
foo
}
- Способ 3: Дайте идентификатору псевдоним при экспорте. (В основном бесполезен, обычно используется псевдоним в файле импорта). Затем в файле импорта вы можете использовать только псевдоним, чтобы получить его.
export {
name as fName,
age as fAge,
foo as fFoo
}
ключевое слово импорта
Ключевое слово import отвечает за импорт контента из другого модуля.
Существует также несколько способов импорта контента:
- Способ 1: импортировать {список идентификаторов} из «модуля».Примечание: {} здесь не является объектом, он только хранит содержимое импортированного списка идентификаторов.
import { name, age } from "./foo.js"
- Способ 2: Псевдоним идентификатора при импорте.
import { name as fName, age as fAge } from './foo.js'
- Способ 3: пройти
*Поместите функцию модуля в объект функции модуля (объект модуля). Затем используйте его с псевдонимом.
import * as foo from './foo.js'
Сочетание экспорта и импорта
Указывает на импорт и экспорт.
import { add, sub } from './math.js'
import {otherProperty} from './other.js'
export {
add,
sub,
otherProperty
}
Эквивалентно
// 导入的所有文件会统一被导出
export { add, sub } from './math.js'
export {otherProperty} from './other.js'
Эквивалентно
export * from './math.js'
export * from './other.js'
почему ты хочешь сделать это?
При разработке и инкапсуляции функциональной библиотеки мы обычно хотим поместить все открытые интерфейсы в один файл.Это позволяет легко задавать унифицированную спецификацию интерфейса и читать ее. В настоящее время мы можем использовать комбинацию экспорта и импорта.
использование по умолчанию
Все функции экспорта, которые мы изучили ранее, называются экспортами:
- Имя было указано при экспорте.
- Необходимо знать конкретное имя при импорте import.
Существует также экспорт, называемый экспортом по умолчанию (экспорт по умолчанию).
// foo.js
const name = "zh"
cconst age = 22
export {
name,
// 或者这样的默认导出
// age as default
}
export default age
// 导入语句: 导入的默认的导出
import foo, {name} from './foo.js'
console.log(foo, name) // 22 zh
- При экспорте по умолчанию имя указывать не нужно.
- Вам не нужно использовать {} при импорте, и вы можете указать имя самостоятельно.
- Это также облегчает нашу совместимость с существующими спецификациями, такими как CommonJS.
Примечание. В модуле может быть только один экспорт по умолчанию.
функция импорта
Загрузка модуля через импорт не может быть размещена в логическом коде, например:
if(true) {
import foo from './foo.js'
}
Почему это происходит?
- Это связано с тем, что модуль ES должен знать свои зависимости, когда он анализируется механизмом JS.
- Поскольку код js в настоящее время не выполняется, невозможно выполнить аналогичную оценку if в соответствии с выполнением кода.
Но в некоторых случаях нам действительно нужно динамически загружать модуль:
- Если в соответствии с различными условиями, динамически выберите путь для загрузки модуля.
В настоящее время нам нужно использовать функцию import() для динамической загрузки. Результатом, возвращаемым функцией импорта, является обещание.
import("./foo.js").then(res => {
console.log("res:", res.name)
})
es11 добавил новое свойство. Само мета-свойство также является объектом: { url: "путь, где находится текущий модуль" }
console.log(import.meta)
Процесс анализа модуля ES
Как модуль ES анализируется браузерами и позволяет модулям ссылаться друг на друга?
Пожалуйста, обратитесь к этой статье для деталей
Процесс парсинга модуля ES можно разделить на три этапа:
- Этап 1: Создание, найдите файл js по адресу, загрузите его и разберите в запись модуля.
- Этап 2: создание экземпляра, создание экземпляра записи модуля, выделение памяти, анализ операторов импорта и экспорта модуля и указание модулю соответствующего адреса памяти.
- Фаза 3: Оценка, запуск кода, вычисление значения и заполнение адреса памяти значением.
Первый этап:
Второй и третий этапы:
Таким образом, из приведенного выше видно, что изменение значения переменной в файле экспорта повлияет на значение в файле импорта. Кроме того, файлу импорта запрещено изменять значения файла экспорта.