你真的懂 export default 吗?
export default A
和export { A as default }
乍一看是一样的,但是里面有一些细微的区别比较容易留坑。本文介绍两种写法的不同之处。
import
语句导入的是引用,不是值
有导出就必然有导入,我们先明确下 import
语句的工作原理。
import { A } from './module.js';
显而易见,在上面的代码中,A
和 ./module.js
中的 A 是相同的。再看这段代码:
const module = await import('./module.js');
const { A: destructuredA } = await import('./module.js');
在这段代码中,module.A
与 A
是相同的,但是因为 destructuredA
是结构赋值,因此就有一些不同了。
我们来看下 ./module.js
:
// module.js
export let A = 'initial';
setTimeout(() => {
A = 'changed';
}, 500);
导入 ./module.js
的代码为 ./main.js
:
// main.js
import { A as importedA } from './module.js';
const module = await import('./module.js');
let { A } = await import('./module.js');
setTimeout(() => {
console.log(importedA); // "changed"
console.log(module.A); // "changed"
console.log(A); // "initial"
}, 1000);
import
语句导入的是引用,也就是说,当 ./module.js
中 A
的值发生变化的时候,./main.js
中也会跟着变化。解构赋值获得的 A
不会变化是因为解构过程中是使用的值赋值给了新变量,而不是引用。
值得注意的是,静态语句 import { A } ...
虽然看着像解构赋值,实际上与解构赋值并不相同。
小结一下:
// 以下代码获得是引用
import { A } from './module.js';
import { A as otherName } from './module.js';
import * as module from './module.js';
const module = await import('./module.js');
// 以下代码获得的是值
let { A } = await import('./module.js');
export default
语句
我们修改下 ./module.js
:
// module.js
let A = 'initial';
export { A };
export default A;
setTimeout(() => {
A = 'changed';
}, 500);
同时也修改 ./main.js
:
// main.js
import { A, default as defaultA } from './module.js';
import anotherDefaultA from './module.js';
setTimeout(() => {
console.log(A); // "changed"
console.log(defaultA); // "initial"
console.log(anotherDefaultA); // "initial"
}, 1000);
输出结果是 "initial"
,为什么呢?
我们知道,我们可以直接 export default 'hello';
但是却不能 export { 'hello' as A }
。规范在这两种语法上有一点不同。export default
后面的将会被作为表达式对待。因此我们可以 export default 'hello';
, 甚至可以 export default 1 + 2;
。因此,在 export default A
中,A
是作为表达式语句使用的,因此使用的是 A 的值。因此,当 A
的值在 setTimeout
中被改变的时候,export default
出去的值并没有变化。
小结一下:
// 引用
import { A } from './module.js';
import { A as otherName } from './module.js';
import * as module from './module.js';
const module = await import('./module.js');
// 值
let { A } = await import('./module.js');
// 导出引用
export { A };
export { A as otherName };
// 导出值
export default A;
export default 'hello!';
export { A as default }
语句
export {}
导出的始终是一个引用,因此:
// module.js
let A = 'initial';
export { A, A as default };
setTimeout(() => {
A = 'changed';
}, 500);
同样,在先前的 ./main.js
中:
// main.js
import { A, default as defaultA } from './module.js';
import anotherDefaultA from './module.js';
setTimeout(() => {
console.log(A); // "changed"
console.log(defaultA); // "changed"
console.log(anotherDefaultA); // "changed"
}, 1000);
小结下:
// 导入引用
import { A } from './module.js';
import { A as otherName } from './module.js';
import * as module from './module.js';
const module = await import('./module.js');
// 导入值
let { A } = await import('./module.js');
// 导出引用
export { A };
export { A as otherName };
export { A as default };
// 导出值
export default A;
export default 'hello!';
export default function
语句
虽然,前面说过 export default
后面的会被作为表达式使用。但是也有一些例外:
// module.js
export default function A() {}
setTimeout(() => {
A = 'changed';
}, 500);
// main.js
import A from './module.js';
setTimeout(() => {
console.log(A); // "changed"
}, 1000);
输出 "changed"
,因为 export default function
有其特殊的语法,在这个语法中,函数是作为引用传递的。
我们稍微做一下修改:
// module.js
function A() {}
export default A;
setTimeout(() => {
A = 'changed';
}, 500);
此时控制台输出 ƒ A() {}
,export
语句不再符合 export default function
语法形式,A
便使用了值传递。
不仅仅 export default function
,export default class
也是同样的表现。
为什么呢?
原因与这些语句当被用作表达式时的表现有关。
function someFunction() {}
class SomeClass {}
console.log(typeof someFunction); // "function"
console.log(typeof SomeClass); // "function"
如果我们将他们变成表达式:
(function someFunction() {});
(class SomeClass {});
console.log(typeof someFunction); // "undefined"
console.log(typeof SomeClass); // "undefined"
function
和 class
语句会在作用域/块中创建标识符,而 function
和 class
语句却不会,尽管他们的函数名和类名可以被使用。
因此,下面代码中:
export default function someFunction() {}
console.log(typeof someFunction); // "function"
如果不进行特殊处理的话,输出的将会是 "undefined"
。
小结如下:
// 导入引用
import { A } from './module.js';
import { A as otherName } from './module.js';
import * as module from './module.js';
const module = await import('./module.js');
// 导入值
let { A } = await import('./module.js');
// 导出引用
export { A };
export { A as otherName };
export { A as default };
export default function A() {}
// 导出值
export default A;
export default 'hello!';
在早些时候,模块中的默认导出是这样的
export default = A
,这样看来,A
被当做表达式会更明显一些。
欢迎关注公众号“众里千寻”或者在我的网站浏览更多更系统的信息。