一、export 命令

export 命令用于规定模块的对外导出接口。

一个独立文件就是一个模块。如果从外部想要获取文件内部所有变量,文件内没有使用export关键字输出该变量(提供对外接口),那么外部就无法获取文件内部变量。

(一)、文件内部不提供对外接口(没有使用export关键字导出)

noExport.mjs 文件内

1
2
3
4
5
6
// noExport.mjs
let demo = 'kinghiee';

function logInfo() {
console.log('logInfo func');
}

import.mjs 文件内

1
2
3
import noExport from './noExport.mjs';

console.log(noExport);

在编辑器中会发现,会提示
提示不是模块
如果要是不顾提示坚持执行会得到如下error

1
2
3
4
5
6
7
8
9
import noExport from './noExport.mjs';
^^^^^^^^
SyntaxError: The requested module './noExport.mjs' does not provide an export named 'default'
at ModuleJob._instantiate (node:internal/modules/esm/module_job:123:21)
at async ModuleJob.run (node:internal/modules/esm/module_job:189:5)
at async Promise.all (index 0)
at async ESMLoader.import (node:internal/modules/esm/loader:530:24)
at async loadESM (node:internal/process/esm_loader:91:5)
at async handleMainPromise (node:internal/modules/run_main:65:12)

注: import是导入命令后面会有介绍

(二)、文件内部使用export

export.mjs 文件内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// export.mjs
let demo = 'kinghiee';

function logInfo() {
console.log('logInfo func');
};

class DemoKinghiee {
constructor() {
console.log('DemoKinghiee 类初始化');
}
}

export { demo, logInfo, DemoKinghiee }; // 推荐这种写法,因为它可以在尾部看清楚都有哪些内容被导出(关注点集中)

import.mjs 文件内

1
2
3
import { demo, logInfo, DemoKinghiee } from './export.mjs';

console.log(demo, logInfo, DemoKinghiee);

注: 因为export.mjs 文件内各个变量都是分个导出的,所以在使用时也是一个一个导入。如果想要import xxx from ‘./export.mjs’;导入,那么后续会介绍export default导出,来提供这种导入能力

(三)、as 关键字变量重命名

如果觉得在export.mjs 文件内使用export导出变量原本的名字实在是逊b了,那么可以使用as关键字进行变量重命名

export.mjs 文件内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let demo = 'kinghiee';

function logInfo() {
console.log('logInfo func');
};

class DemoKinghiee {
constructor() {
console.log('DemoKinghiee 类初始化');
}
}

export {
demo as kingDemo,
logInfo as kingLogInfo,
DemoKinghiee as kingDemoKinghiee
}; // 推荐这种写法,因为它可以在尾部看清楚都有哪些内容被导出(关注点集中)

import.mjs 文件内

1
2
3
import { kingDemo, kingLogInfo, kingDemoKinghiee } from './export.mjs';

console.log(kingDemo, kingLogInfo, kingDemoKinghiee);

注:导入时的名字应该和导出时的名字一致

(四)、export default

从上面的例子可以看出,使用import命令时用户需要知道所要加载的变量名或函数名,否则就没有办法加载。换句话说就是得全面知道要加载的文件中都有哪些内容是被export导出的。这无形之中就要求使用者去了解模块内容,这样才能找到要用的东西。但是很多用户肯定想要快速上手,未必会愿意全面了解模块都export什么。为了让用户快速使用,这时可以使用export default为模块指定默认输出。

exportDefault.mjs 文件内

1
2
3
4
// exportDefault.mjs
export default function log() {
console.log('log');
}

import.mjs 文件内

1
2
3
import test from './exportDefault.mjs';

console.log(test);

可以看出当使用export default导出时,在使用的import后面不出现大括号(‘{}’),而且导入时也可以对原函数进行重命名,这时就不需要知道原模块输出的函数名。同时需要注意的是导出匿名函数也是可以的

exportDefault.mjs 文件内

1
2
3
4
// exportDefault.mjs
export default function () { // 导出匿名函数
console.log('anonymous');
}

注:export default命令用于指定模块的默认输出,可知一个模块最多只能有一个默认输出,因此export default命令只能用一次,这也是import导入相应模块后面没有大括号的原因

1. 在一个文件中不能使用多次export default

1
2
3
4
5
6
7
export default function () {
console.log('anonymous');
}

export default function log() {
console.log('log');
}

一个文件内多次使用export default

2. export default 的本质

export default 本质上是输出一个叫作default的变量或方法,然后系统允许我们为它取任意名字。

exportDefault.mjs 文件内

1
2
3
4
5
6
// exportDefault.mjs
function log() {
console.log('log');
}

export { log as default }; // export default导出

import.mjs 文件内

1
2
3
import test from './exportDefault.mjs';

console.log(test);

默认导出本质

因为export default本质是输出一个叫作default的变量或方法(这是后面不能跟变量声明语句的原因),那么下面的写法就成为了可能

写法1

1
2
3
let a = 1;

export default a;

写法2

1
export default 40;

上面两种写法可以这么理解:export default是将该命令后面的值赋给default变量之后再默认,所以上面的写法没有问题,但是下面的写法是有问题的。

错误写法

1
export default let a = 1;

(五)、export导出需要注意的几个点

1. export 命令规定的是对外的接口,必须与模块内部的变量建立一一对应关系

正确写法1

1
export let m = 1;

正确写法2

1
2
3
let m = 1;

export { m };

正确写法3

1
2
3
let m = 1;

export { m as one };

错误写法1

1
export 1;

错误写法2

1
2
let m = 1;
export m;

解释: 错误写法中使用export导出的直接就是数值1,所以没有对应关系。然而比如正确写法1中建立了一一对应关系。效果如下

正确写法

里面的具体实现细节可以参考ECMAScript官方文档

2. export输出的接口与对应额值是动态绑定关系

可以通过接口取到模块内部实时的值

1
2
export let foo = 'bar';
setTimeout(() => foo = 'baz', 500);

动态值
可以看到最后值发生了变化。

3. export出现的位置

export可以出现在模块中的任何位置,只能处于模块顶层。

二、import 命令

(一)、import 加载export导出的变量或方法

export.mjs 文件内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// export.mjs
let demo = 'kinghiee';

function logInfo() {
console.log('logInfo func');
};

class DemoKinghiee {
constructor() {
console.log('DemoKinghiee 类初始化');
}
}

export { demo, logInfo, DemoKinghiee }; // 推荐这种写法,因为它可以在尾部看清楚都有哪些内容被导出(关注点集中)

import.mjs 文件内

1
2
3
import { demo, logInfo, DemoKinghiee } from './export.mjs';

console.log(demo, logInfo, DemoKinghiee);

在export.mjs 文件内用export导出的内容,可以使用import 后面加大括号的形式一一导入,其中要注意的是大括号中的变量名必须与被导入模块对外接口的名称相同。如果说你想要重命名可以使用as来进行重命名操作

1
2
3
import { demo as demo1, logInfo as logInfo1, DemoKinghiee as DemoKinghiee1 } from './export.mjs';

console.log(demo1, logInfo1, DemoKinghiee1);

(二)、import 整体加载

如果觉得使用import一一对应export导出值太麻烦的话,可以使用*来指定一个对象,所有输出值都加载在这个对象上。

export.mjs 文件内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// export.mjs
let demo = 'kinghiee';

function logInfo() {
console.log('logInfo func');
};

class DemoKinghiee {
constructor() {
console.log('DemoKinghiee 类初始化');
}
}

export { demo, logInfo, DemoKinghiee }; // 推荐这种写法,因为它可以在尾部看清楚都有哪些内容被导出(关注点集中)

import.mjs 文件内

1
2
3
import * as all from './export.mjs';

console.log(all);

全部加载

export defalut 导出也能别被加载吗?(可以的)

export.mjs 文件内中加入export defalut

1
export default 1;

export default 全部加载
这也正好说明了export default的本质

三、ES6 module严格模式和静态化

ES6模块自动采用严格模式,不管有没有在模块头部加上”use strict”。

ES6设计思想是尽量静态化,使得编译时就能确定模块的依赖关系。这也使tree shaking成为可能

四、export与import的复合写法

如果在一个模块中先输入后输出同一个模块,那么import和export语句可以写在一起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 例如
export { a, b } from 'somethingModule';

// 例如
import { a, b } from 'somethingModule';
export { a, b };

// 例如
export { c as aliasC } from 'somethingModule';

// 例如
export { d as default } from 'somethingModule';

// 例如
export { default as d } from 'somethingModule';

五、import() 动态导入

标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入。下面的是你可能会需要动态导入的场景:

  • 当静态导入的模块很明显的降低了代码的加载速度且被使用的可能性很低,或者并不需要马上使用它。
  • 当静态导入的模块很明显的占用了大量系统内存且被使用的可能性很低。
  • 当被导入的模块,在加载时并不存在,需要异步获取。
  • 当导入模块的说明符,需要动态构建。(静态导入只能使用静态说明符)
  • 当被导入的模块有副作用(这里说的副作用,可以理解为模块中会直接运行的代码),这些副作用只有在触发了某些条件才被需要时。(原则上来说,模块不能有副作用,但是很多时候,你无法控制你所依赖的模块的内容)
1
2
3
4
5
6
import('/modules/my-module.js')
.then((module) => {
});

// 或者
let module = await import('/modules/my-module.js');

参考:
1.《ES6标准入门》 第3版
2. MDN