最近小编看到一篇比较有意思的文章,挑了些重点大概翻译了一下分享出来。以下为原文:
只用 export
有一个 ESM 模块,代码如下:
export let thing = 'initial';
setTimeout(() => {
thing = 'changed';
}, 500);
现创建一个 main.mjs :
import { thing as importedThing } from './module.mjs';
const module = await import('./module.mjs');
let { thing } = await import('./module.mjs');
const { thing: destructuredThing } = await import('./module.mjs');
setTimeout(() => {
console.log(importedThing);
console.log(module.thing);
console.log(thing);
console.log(destructuredThing);
}, 1000);
使用 Node.js 版本 16.4.2 执行 node main.mjs 结果:
changed
changed
initial
initial
输出结果和你预期一样么?
import { thing as importedThing } from './module.mjs';
const module = await import('./module.mjs');
这两行代码比较好理解,import 语句就是对 export 对象的引用,一旦 export 对象变更,对应 import 的 importedThing 和 module 也同步会更新。
let { thing } = await import('./module.mjs');
const { thing: destructuredThing } = await import('./module.mjs');
这两行其实就是正常的解构,解构时获取的是当前的值,也就是 "initial"。
export default
我们对代码稍微做一下改动(module.mjs):
let thing = 'initial';
export { thing };
export default thing;
setTimeout(() => {
thing = 'changed';
}, 500);
main.mjs 也做一下变动:
import { thing, default as defaultThing } from './module.mjs';
import anotherDefaultThing from './module.mjs';
setTimeout(() => {
console.log(thing);
console.log(defaultThing);
console.log(anotherDefaultThing);
}, 1000);
node main.mjs 输出结果:
changed
initial
initial
defaultThing 居然不是引用?"export {}" 居然是一个引用?原来 export default 只 export 当前的值,不是一个引用。
如果再改动一下代码(module.mjs):
let thing = 'initial';
export { thing, thing as default };
setTimeout(() => {
thing = 'changed';
}, 500);
node main.mjs 输出结果:
changed
changed
changed
怎么都输出 "changed" 了?这里通过 "thing as default" 把 default 也变成一个引用了。
那么如果是 function 呢?我们来改动一下代码(module.mjs):
export default function thing() {}
setTimeout(() => {
thing = 'changed';
}, 500);
main.mjs 也改动一下:
import thing from './module.mjs';
setTimeout(() => {
console.log(thing);
}, 1000);
node main.mjs 输出结果:
changed
为什么不是 function thing 而是 "changed" ?这里 export default function 被当成一个表达式处理,作为一个特殊语义 export 了一个引用,导致值从 function 变成 "changed"。
如果改动一下(module.mjs):
function thing() {}
export default thing;
setTimeout(() => {
thing = 'changed';
}, 500);
这样,这里就直接 export 了 thing 函数,setTimeout 执行也不会影响 main.mjs 中 import thing 函数。因为这里 thing 作为值被 export 出去了。
汇总一下知识点
// 导出对象,且引用
import { thing } from './module.js';
import { thing as otherName } from './module.js';
import * as module from './module.js';
const module = await import('./module.js');
// 通过解构将值重新赋给 thing 引用关系断开
let { thing } = await import('./module.js');
// 这里都是 export 一个引用
export { thing };
export { thing as otherName };
export { thing as default };
export default function thing() {}
// 只 export 当前的值
export default thing;
export default 'hello!';
循环引用
典型的 case:
// module.mjs
import { hello } from './main.mjs';
hello();
export function foo() {
console.log('foo');
}
// main.mjs
import { foo } from './module.mjs';
foo();
export function hello() {
console.log('hello');
}
以上代码使用 node main.mjs 是可以正常执行的。
再看一下另一个 case:
// module.mjs
import { hello } from './main.mjs';
hello();
export const foo = () => console.log('foo');
// main.mjs
import { foo } from './module.mjs';
foo();
export const hello = () => console.log('hello');
这里就执行出错了(Cannot access xxx before initialization)。
如果使用 default 改动一下代码:
// module.mjs
import hello from './main.mjs';
hello();
function foo() {
console.log('foo');
}
export default foo;
// main.mjs
import foo from './module.mjs';
foo();
function hello() {
console.log('hello');
}
export default hello;
这个代码就又可以正常执行了。
还是和上面提到的 export default 一样,这里默认一个 ESM 模块会有一个隐藏值,在访问前默认提前初始化。Cannot access xxx before initialization 是因为export const 直接返回值,这样在 module.mjs 中提前访问执行 hello 函数,这里就出错了。这里如果使用 export { hello as default},就不会说 access before initialization,因为 export { hello as default} 由于函数引用,会自动提升初始化时机。
感觉又多了一个面试时可以讨论的话题!
大家可以阅读原文查看。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8