ES6中的`export const`与`export default`有何区别?

334

我正在尝试确定这两者之间是否存在任何大的区别,除了可以通过以下方式使用export default导入:

import myItem from 'myItem';

并且使用export const,我可以这样做:

import { myItem } from 'myItem';

除此之外,还有什么区别和/或使用情况吗?


2
使用 const 将使标识符只读。因此,在原始值的情况下,您可以将其视为不可变。请注意,值本身并非不可变,因此对象、数组等可以更改,但不能重新分配。 - spmurrayzzz
6
值得一提的是,导入绑定与const一样是不可变的。 - Felix Kling
1
@Bergi:没错,这就是我说“导入绑定”的原因 ;) - Felix Kling
@Bergi:我们已经有一个涵盖默认与命名导出的问题了,不是吗?我找不到任何东西,但感觉这个问题肯定已经被问过了。 - Felix Kling
@FelixKling:你是指ES6模块导出选项吗? - Bergi
显示剩余3条评论
6个回答

476

这是一个具名导出与默认导出的区别。export const 是一种具名导出方式,用于导出一个或多个常量声明。

需要强调的是:关键字 export 很重要,因为 const 用于声明一个或多个常量声明。而 export 还可以应用于其他声明,比如类或函数声明。

默认导出 (export default)

每个文件只能有一个默认导出。当你导入时,必须指定名称并像这样导入:

import MyDefaultExport from "./MyFileWithADefaultExport";

你可以给它任何你喜欢的名字。

命名导出(export

通过命名导出,你可以在一个文件中拥有多个命名导出。然后在大括号内导入你想要的特定导出:

// ex. importing multiple exports:
import { MyClass, MyOtherClass } from "./MyClass";
// ex. giving a named import a different name by using "as":
import { MyClass2 as MyClass2Alias } from "./MyClass2";

// use MyClass, MyOtherClass, and MyClass2Alias here

或者在同一条语句中使用默认导出和命名导入:

import MyDefaultExport, { MyClass, MyOtherClass} from "./MyClass";

命名空间导入

从一个对象中导入文件的所有内容也是可能的:

import * as MyClasses from "./MyClass";
// use MyClasses.MyClass, MyClasses.MyOtherClass and MyClasses.default here

注意事项

  • 因为默认导出的用例更为常见,所以语法更偏向于使用默认导出,因为它们略微更加简洁。(参见此处的讨论)。
  • 默认导出实际上是一个具有名称为default的命名导出,因此您可以使用命名导入来导入它:

    import { default as MyDefaultExport } from "./MyFileWithADefaultExport";
    

66
export default 会影响导入所需的语法,允许通过在 import 中选择名称来导入任何已经被导出的东西,无论它在导出时使用的名称是什么,只要它被标记为 "default"。一个有用的用例是可以导出一个匿名函数,而不必显式地为其命名,只有在导入该函数时,才需要为其赋予一个名称:

示例:

导出两个函数,其中一个是default

export function divide( x ){
    return x / 2;
}

// only one 'default' function may be exported and the rest (above) must be named
export default function( x ){  // <---- declared as a default function
    return x * x;
}

导入上述函数。为default函数起一个名称:

// The default function should be the first to import (and named whatever)
import square, {divide} from './module_1.js'; // I named the default "square" 

console.log( square(2), divide(2) ); // 4, 1

使用 {} 语法导入函数(或变量)时,它意味着无论导出内容如何命名,导入时必须使用完全相同的名称,否则导入将不起作用。


错误示例:


  1. The default function must be first to import

    import {divide}, square from './module_1.js
    
  2. divide_1 was not exported in module_1.js, thus nothing will be imported

    import {divide_1} from './module_1.js
    
  3. square was not exported in module_1.js, because {} tells the engine to explicitly search for named exports only.

    import {square} from './module_1.js
    

它并不意味着只导出一个东西。你可以在同一个模块中有多个命名的导出和一个默认的导出。默认导出仅仅意味着 - 如果你在导入时没有指定名称,它就是默认导出,例如 import something from 而不是 import { somethingNamed } from - Andris

32

更重要的区别是:export default导出值,而export const/export var/export let导出引用(也被称为活绑定)。在nodejs中尝试下面的代码(使用13或以上版本以默认启用es模块):

// a.mjs

export let x = 5;
// or
// let x = 5;
// export { x }

setInterval(() => {
  x++;
}, 1000);

export default x;
// index.mjs
import y, { x } from './1.mjs';

setInterval(() => {
  console.log(y, x);
}, 1000);
# install node 13 or above
node ./index.mjs

我们应该得到以下输出:

6 5
7 5
8 5
...
...

为什么我们需要这种差异

很可能,export default 是为了与 commonjs 的 module.exports 兼容而使用的。

如何在打包工具(rollup、webpack)中实现这一点

对于上述代码,我们使用 rollup 进行打包。

rollup ./index.mjs --dir build 

生成的输出为:

// build/index.js

let x = 5;
// or
// let x = 5;
// export { x }

setInterval(() => {
  x++;
}, 1000);

var y = x;

setInterval(() => {
  console.log(y, x);
}, 1000);

请注意var y = x语句,这是默认设置。

webpack具有类似的构建输出。当大量模块添加到构建中时,连接文本是不可持续的,打包工具将使用Object.defineProperty来实现绑定(或在webpack中称为"harmony exports")。请在下面的代码中查找详细信息:

main.js
...
/******/    // define getter function for harmony exports
/******/    __webpack_require__.d = function(exports, name, getter) {
/******/        if(!__webpack_require__.o(exports, name)) {
/******/            Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/        }
/******/    };
...
// 1.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "x", function() { return x; });
let x = 5;
// or
// let x = 5;
// export { x }

setInterval(() => {
  x++;
}, 1000);

/* harmony default export */ __webpack_exports__["default"] = (x);


/***/ })
]]);

请查找/* harmony export (binding) *//* harmony default export */之间的不同行为。

ES模块本地实现

Mozilla的es-modules-a-cartoon-deep-dive介绍了关于ES模块的为什么、什么以及如何。


23

小提示:请记住,当您从默认导出中导入时,名称是完全独立的。这实际上会影响重构。

假设您有一个类Foo,并对应有一个导入:

export default class Foo { }

// The name 'Foo' could be anything, since it's just an
// Identifier for the default export
import Foo from './Foo'

如果你对Foo类进行重构,改名为Bar并且重命名文件,大多数IDE将不会修改你的导入语句。因此,你最终会得到以下结果:

export default class Bar { }

// The name 'Foo' could be anything, since it's just an
// Identifier for the default export.
import Foo from './Bar'

特别是在TypeScript中,我非常赞赏命名导出和更可靠的重构。区别仅在于缺少default关键字和花括号。顺便提一下,现在由于有类型检查,这也可以防止你在导入时犯错。

export class Foo { }

//'Foo' needs to be the class name. The import will be refactored
//in case of a rename!
import { Foo } from './Foo'

2
“'Foo'需要成为类名。” - 不!您可以像使用默认导出一样使用import { Foo as Anything } from …,也可以使用import Anything from … - Bergi
2
你可以用 as 重命名它,但这并不是源代码注释的重点。感谢你的投票;p - Philipp Sumi
2
我没有点踩,但我不确定那个论点是否令人信服。我不知道当重构单个模块时是否想要我的IDE重命名所有导入,这正是模块化的目的 :-) 而且这似乎更像是一个IDE的“问题”,而不是选择导出样式的原因... - Bergi
3
我同意在这里使用命名导出是为了开发者的用户体验,但是你可以认为Typescript本身就是为此而存在。我经常进行重构,考虑到通常每个文件中只有一个类(在我的情况下是React组件),我绝对希望在重命名组件时导入也要跟随变化,以避免产生不连贯感。当然,这可能因个人开发者而异,这取决于是否有意义。 - Philipp Sumi
1
我找到了一篇文章,链接为 https://blog.neufund.org/why-we-have-banned-default-exports-and-you-should-do-the-same-d51fdc2cf2ad,文章中也提到了同样的观点。或许一个合理的立场是:我们应该使用 export default 来导出一个项目的主要对象,特别是从 npm 包中(它代替了 module.exports =)。但在项目内部,最好仅使用命名导出。 - Paleo
感谢您包含相应导出示例! - Travis Hohl

14

来自文档

命名导出非常有用,可用于导出多个值。在导入期间,可以使用相同的名称引用相应的值。

关于默认导出,每个模块只有一个默认导出。默认导出可以是函数、类、对象或其他任何值,此值应被视为“主”导出值,因为它最简单易懂。


4

当你使用default时,它被称为默认导出。每个文件只能有一个默认导出,并且您可以使用任何名称在另一个文件中导入它。当您不使用default时,它被称为命名导出,您必须在另一个文件中使用相同的名称和花括号导入它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接