предисловие
В ES6 мы знаем, что import и export заменяют require и module.exports для импорта и экспорта модулей, но если вы не понимаете функции модуля ES6, код может привести к невероятным результатам.Я пройдусь по этой статье ниже. особенности механизма модуля ES6 для вас.
Возможности модуля ES6
Я не буду вводить базовое использование модуля ES6.Если вы не использовали модули ES6, рекомендуется прочитать:Начало работы с ECMAScript 6 — синтаксис модуля
Говоря о возможностях модуля ES6, давайте поговорим о разнице между модулями ES6 и модулями CommonJS.
Модули ES6 отличаются от модулей CommonJS в следующих двух аспектах:
- Модули ES6 выводят ссылку на значение, а выходной интерфейс динамически привязывается, в то время как CommonJS выводит копию значения.
- Модули ES6 выполняются во время компиляции, а модули CommonJS всегда загружаются во время выполнения.
Как это понимать? Давайте рассмотрим это шаг за шагом:
Копия выходного значения CommonJS
Прежде всего, в модулях CommonJS, если вам требуется модуль, это эквивалентно тому, что вы выполняете код файла и, наконец, получаете копию объекта module.exports, выводимого модулем.
// a.js
var b = require('./b');
console.log(b.foo);
setTimeout(() => {
console.log(b.foo);
console.log(require('./b').foo);
}, 1000);
// b.js
let foo = 1;
setTimeout(() => {
foo = 2;
}, 500);
module.exports = {
foo: foo,
};
// 执行:node a.js
// 执行结果:
// 1
// 1
// 1
Как видно из приведенного выше кода, то, что вы получаете, является просто копией выходного объекта модуля, foo в b больше не связано с foo в a, поэтому, если вы хотите динамически получить значение в модуле в CommonJS, то необходимо использовать возможность отложенного выполнения функций.
// a.js
var b = require('./b');
console.log(b.foo());
setTimeout(() => {
console.log(b.foo());
console.log(require('./b').foo());
}, 1000);
// b.js
let foo = 1;
setTimeout(() => {
foo = 2;
}, 500);
module.exports = {
foo: () => {
return foo;
},
};
// 执行:node a.js
// 执行结果:
// 1
// 2
// 2
Итак, мы можем подытожить:
- В модулях CommonJS место, где модуль вводится с помощью require, будет влиять на результат вывода, и будет сгенерирована копия значения.
- Модули, повторно введенные модулями CommonJS, не будут выполняться повторно, и повторное получение модуля приведет только к получению копии ранее полученного модуля.
Ссылки на выходные значения ES6
Однако в модулях ES6 вместо генерации копий выходных объектов мы динамически связываем значения в модулях.
// a.js
import { foo } from './b';
console.log(foo);
setTimeout(() => {
console.log(foo);
import('./b').then(({ foo }) => {
console.log(foo);
});
}, 1000);
// b.js
export let foo = 1;
setTimeout(() => {
foo = 2;
}, 500);
// 执行:babel-node a.js
// 执行结果:
// 1
// 2
// 2
Статическая компиляция ES6, загрузка во время выполнения CommonJS
Что касается второго пункта, выполнение модуля ES6 во время компиляции приводит к следующим двум характеристикам:
- Команда импорта будет статически проанализирована движком JavaScript и выполнена перед другим содержимым модуля.
- Команда экспорта имеет эффект продвижения объявлений переменных.
импорт имеет приоритет:
С первой точки импорт модуля импорта в любом месте файла будет перенесен в начало файла.
// a.js
console.log('a.js')
import { foo } from './b';
// b.js
export let foo = 1;
console.log('b.js 先执行');
// 执行结果:
// b.js 先执行
// a.js
Из результатов выполнения мы можем интуитивно увидеть, что, хотя импорт в модуле а введен позже, чем console.log('a'), он статически анализируется движком JS и упоминается в начале выполнения модуля, т.е. лучше чем в модуле исполнение других частей.
Поскольку импорт выполняется статически, импорт имеет эффект подъема, то есть расположение команды импорта в модуле не влияет на вывод программы.
Обновление экспорта вариационной декларации:
Обычный модуль импорта не может видеть характеристики продвижения объявлений переменных, которые необходимо просматривать через циклическую загрузку зависимостей.
// 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 также относится к модулю а. Переменные, объявленные с помощью экспорта, также превосходят выполнение другого содержимого модуля, но конкретное назначение переменных нужно дождаться соответствующего выполнения, когда код. (Конечно, объявления функций отличаются от объявлений выражений, которые аналогичны функциям JS, поэтому я не буду подробно объяснять их здесь)
Хорошо, после разговора о различиях между модулями ES6 и модулями CommonJS, давайте поговорим об одних и тех же моментах:
Модули не выполняются повторно
Это легко понять, будь то модуль ES6 или модуль CommonJS, когда вы повторно импортируете один и тот же модуль, модуль будет выполнен только один раз.
// a.js
import './b';
import './b';
// b.js
console.log('只会执行一次');
// 执行结果:
// 只会执行一次
В сочетании с упомянутой выше функциями, давайте посмотрим на более классический пример, круговую зависимость. После того, как вы понимаете, упомянутые выше функции, результат выполнения кода циркулярной зависимости модуля будет легко понять в следующий раз.
Круговые зависимости модуля CommonJS
Давайте посмотрим на следующий пример:
// 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
Это легко понять в сочетании с функциями, упомянутыми ранее: когда вы хотите импортировать модуль a из b, поскольку узел уже загрузил модуль a ранее, он не будет повторять выполнение модуля a, а напрямую сгенерирует текущий модуль a. Копия выплевываемого объекта module.exports, потому что модуль a вводит модуль b перед переназначением done, поэтому значение done в module.exports, которое в настоящее время выводится из модуля a, по-прежнему ложно. И когда значение done модуля b выводится в модуль a, модуль b был выполнен, поэтому значение done в модуле b истинно.
Из вышеприведенного процесса выполнения мы видим, что в спецификации CommonJS при встрече с оператором require() будет выполняться код в модуле require, а результат выполнения будет кэшироваться.Вместо этого берите кэшированный результат напрямую. Из-за этого нет бесконечного цикла вызовов, когда есть циклическая зависимость. Хотя этот механизм загрузки модуля может избежать ситуации, когда циклические зависимости сообщают об ошибках, небольшая небрежность может привести к тому, что код не будет выполняться так, как мы предполагали. Поэтому при написании кода требуется тщательное планирование, чтобы убедиться, что зависимости модуля цикла работают правильно.
Итак, есть ли способ избежать путаницы при наличии циклической зависимости? Одно из решений состоит в том, чтобы сначала написать синтаксис экспорта для каждого модуля, затем написать оператор require и использовать механизм кэширования CommonJS для экспорта содержимого, которое нужно экспортировать, прежде чем require() других модулей, чтобы гарантировать, что другие модули могут использовать его. получить правильное значение. Например:
// a.js
exports.done = true;
let b = require('./b');
console.log(b.done)
// b.js
exports.done = true;
let a = require('./a');
console.log(a.done)
Этот метод написания прост и понятен, но недостатком является то, что метод написания каждого модуля нужно менять, и большинство студентов привыкли писать оператор require в начале файла.
Зависимость цикла модуля ES6
Как и модули CommonJS, ES6 больше не будет выполнять повторно загруженные модули, а благодаря функции динамической привязки вывода ES6 ES6 может гарантировать, что ES6 сможет получать текущие последние значения других модулей в любое время.
// a.js
console.log('a starting')
import {foo} from './b';
console.log('in b, foo:', foo);
export const bar = 2;
console.log('a done');
// b.js
console.log('b starting');
import {bar} from './a';
export const foo = 'foo';
console.log('in a, bar:', bar);
setTimeout(() => {
console.log('in a, setTimeout bar:', bar);
})
console.log('b done');
// babel-node a.js
// 执行结果:
// b starting
// in a, bar: undefined
// b done
// a starting
// in b, foo: foo
// a done
// in a, setTimeout bar: 2
Если вы не понимаете результат выполнения, это означает, что вы не понимаете функции модуля ES6, упомянутые выше, пожалуйста, прочитайте его еще раз!
динамический импорт()
Модули ES6 статически анализируются во время компиляции, что имеет приоритет над другим содержимым модуля, поэтому мы не можем написать следующий код:
if(some condition) {
import a from './a';
}else {
import b from './b';
}
// or
import a from (str + 'b');
Из-за статического анализа во время компиляции мы не можем использовать условные операторы или модули сращивания строк, потому что это результаты, которые необходимо определить во время выполнения.Модули ES6 не разрешены, поэтому появился динамический импорт ().
Import () позволяет динамически импортировать модули ES6 во время выполнения, и когда вы думаете об этом, вы также можете подумать о синтаксисе потребностей. Tensure, но они служат очень разными целями.
- Появление require.ensure — это продукт webpack, потому что браузеру нужен асинхронный механизм, который можно использовать для загрузки модулей асинхронно, тем самым уменьшая размер исходного загружаемого файла, поэтому, если он на стороне сервера, require .ensure бесполезен.Теперь, поскольку нет асинхронной загрузки модулей на стороне сервера, модули могут быть загружены синхронно для соответствия сценариям использования. Модули CommonJS могут подтверждать загрузку модуля во время выполнения.
- Import() отличается.В основном это связано с тем, что модули ES6 не могут определить ссылочное отношение модулей во время выполнения, поэтому необходимо ввести import().
Давайте сначала посмотрим на его использование:
- Dynamic import() предоставляет API на основе Promise.
- Динамический импорт() можно использовать в любом месте скрипта.
- 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, который проверяет, какое обещание было разрешено или отклонено первым. Мы можем использовать 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'),
]);
}
Динамический импорт() дает нам дополнительную функциональность для асинхронного использования модулей ES. Динамическая или условная загрузка их в зависимости от наших потребностей позволяет нам быстрее и качественнее создавать больше полезных приложений.
конец
Говоря об этом, мы переходим от ES6, механизма загрузки модуля CommonJS, к динамическому импорту модуля import() После прочтения этой статьи я полагаю, что вы сможете лучше понять механизм загрузки модуля ES6, и у вас будет собственное мнение о некоторых странных выводах. Я надеюсь, что эта статья поможет вам Помогите!