可以参考ES6的模块设计, 不同于Nodejs的动态, ES6的模块技术竟然是静态的

导出什么东西?

我们首先思考一下, 我们在使用export的时候, 会用它导出什么东西。答案是名字(name)和值(value)。毕竟除了关键字, 那些加减乘除, 还有一些括号符号之外, 名字和值就是你代码出现且有用的东西了。

1
2
3
4
5
6
7
8
9
10
11
12
//基础声明的name
export <let/const/var> x;
export function x(){};
export class x;
export {x, y, z ...};

//重命名
export {x as y, ...};
export {x as default, ...};

//导出其它模块的名字
export ... from ...;

首先关于名字比较容易理解, 做一个表登记一下, 然后读取, 就像编程语言中的环境(env)一样, 那么值(value)如何理解, 值压根没有名字, 所以为了这一点JavaScript为导出的值 设计了一个特殊的名字叫做default。这样值就有了一个默认的名字叫做default。

所以你可以写出下面这种代码:

1
2
3
4
5
6
7
8
9
10
11
export default expression;

export default 2;
export default "hello, world";


//对象字面量也是一个值
export default {};

//匿名函数也是一个值
export default function() {};

导出语句与导入语句的处理逻辑

我们导出一个名字之后, 还需要使用它 比如

1
2
3
4
5
6
模块1
export let a = 1;

模块2
import a from "./xxx.js"
console.log(a + 1);

由于其他模块需要使用, 我们就需要为导出的名字绑定一个值, 所以export的处理逻辑为

  1. 形成一个模块表(name), 登记一个名字
  2. 为名字绑定一个值, 这是为执行期间的东西

可以使用JavaScript简单模拟一下:

1
2
let table = [];
table.push({name:a, value: 1});

对应的模块2的import处理逻辑为

  1. 添加对该模块的一个依赖, 这样我们就可以根据import形成模块依赖树, 找到跟模块, 然后加载了.
  2. 在当前模块环境(scope)声明一个名字

所以总结export/import就是:

  1. 根据export形成一个名字表
  2. 根据import形成一个依赖

所以说ES6的模块系统export/import完全是静态的, 因为根本就没有出现像a+1这样执行代码.但是到了执行阶段就要做下面的事情:

  • 找到静态装载阶段的模块树, 并且遍历
  • 执行最顶层的代码
  1. 为名字绑定值, 包括为导出值绑定default这个特殊的名字
  2. 使用名字