#javascript #es6-modules
Вопрос:
Рассмотрим следующий пример:
// module.mjs
export let member = "initial"
setTimeout(() => {
member = "mutated"
}, 2000)
// index.mjs
import { member } from "./module.mjs"
setInterval(() => {
console.log(member)
}, 300);
Если вы запустите index.mjs
его, он напечатает "initial"
несколько раз и после 2-секундного тайм-аута начнет печать "mutated"
.
Это меня удивляет. Я ожидал, что он просто продолжит печатать "initial"
, так как ссылка на member
изменилась. Я бы не удивился, если бы мой index.mjs
выглядел так
// index.mjs
import * as allExports from "./module.mjs"
setInterval(() => {
console.log(allExports.member)
}, 300);
Ссылка на allExports
не изменилась. Таким образом, получив доступ member
в качестве свойства, я получаю доступ к новой ссылке. И вполне справедливо, что это приводит к тому же результату.
Но почему это работает и в первом случае? Обновляются ли ссылки на импорт каждый раз, когда создается контекст для вызова функции? Ну, тогда он также должен работать с экспортом по умолчанию. Однако после такой корректировки кода:
// module.mjs
let member = "initial"
export default member
setTimeout(() => {
member = "mutated"
}, 2000)
// index.mjs
import member from "./module.mjs"
setInterval(() => {
console.log(member)
}, 300);
index.mjs
просто продолжает печатать "initial"
в соответствии с моей интуицией. Хм, может быть, есть принципиальная разница между экспортом по умолчанию и экспортом по имени. Что, если я сделаю это:
// module.mjs
export let member = "initial"
setTimeout(() => {
member = "mutated"
}, 2000)
// index.mjs
import * as allExports from "./module.mjs"
let { member } = allExports
setInterval(() => {
console.log(member)
}, 300);
Вау, он продолжает печатать "initial"
. Вот тут я действительно запутался. Я всегда предполагал, что
import { member } from "./module.mjs"
это просто сокращение от импорта всего named-exports
как объекта, а затем выполнения деструктурирования.
Поэтому мой вопрос таков: какой основополагающий принцип оправдывает такое уникальное поведение named-exports
?
Комментарии:
1. Шаг 1, когда основные принципы сбивают вас с толку: быстро прочтите официальную спецификацию, чтобы убедиться, что вы просто неправильно поняли механизм. Даже что-то вроде developer.mozilla.org/en-US/docs/web/javascript/reference/… сделает следующее: » Инструкция export используется при создании модулей JavaScript для экспорта живых привязок к функциям, объектам или примитивным значениям из модуля «.
2. (С Minusfour, объясняющим, почему allExports делает что-то другое со ссылкой на спецификацию)
Ответ №1:
Причина этого в том, что технически модули ES6 экспортируют и импортируют привязки, а не значения. Когда вы это сделаете:
export let test = 0
Вам нужно думать, что вы test
не экспортируете 0
. Это означает, что любое изменение переменной будет отражено на их импорте.
Причина, по которой это продолжает печататься initial
:
// index.mjs
import * as allExports from "./module.mjs"
let { member } = allExports
setInterval(() => {
console.log(member)
}, 300);
Это потому, что allExports
это особый объект. Вы создаете здесь новую привязку, и значение копируется, но вы теряете импортированную привязку.
Комментарии:
1. Интересный. Но почему это не относится к экспорту по умолчанию?
2. Об этом я ничего не знаю. Я предполагаю, что они хотели поддерживать уровень совместимости с другими модулями (например, модулями узлов), которые экспортируют не привязки, а единственное значение. У них есть местное название, хотя в соответствии со спецификацией.
3. И на самом деле, у вас может быть экспорт по умолчанию с привязкой. Если экспорт по умолчанию представляет собой объявление функции (с асинхронным, генератором или их комбинацией) или объявление класса, вы экспортируете привязку указанного объявления. Они также изменчивы, поэтому это работает.