export default 的不同行为

286次阅读  |  发布于3年以前

最近小编看到一篇比较有意思的文章,挑了些重点大概翻译了一下分享出来。以下为原文:

只用 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