forked from shiiiiiiji/IFEEES
-
Notifications
You must be signed in to change notification settings - Fork 0
Open
Labels
Description
ES6 模块两个最主要的特点
相较于CommonJS,ES6 模块有两个最主要的特点。
1.静态加载
ES6 模块的最大特点就是静态加载,也就是在编译时确定模块之间的依赖关系以及导入和导出的接口(变量、函数、类),ES6 模块不能在运行时动态加载。
2.ES6 模块不是对象
ES6 模块不是对象,而是通过export导出接口,再通过import导入。顶层的this指向undefined。
export & import
1.export
export的作用是导出接口(变量、函数、类)
// 第一种用法:export+声明语句
export let a = 1;
// 第二种用法:export+大括号
let b = 1;
export { // 这里的大括号不是对象
b
}
// 第三种用法:使用 as 重命名
let c = 1;
export { // 对外输出的是 cx,c 不再可见
c as cx
}
导出 函数 和 类 的用法也是这样。
不管是export还是import都只能处于模块顶层,如果处于块级作用域内,会报错。
import在编译阶段生成只读引用,执行时,根据这个只读引用,到被加载的模块里面取值。接口与模块内部变量是一一对应关系,export导出的是接口,不能是表达式。
// 错
export 1;
// 错
let a = 1;
export a;
// 错
function b () {
}
export b;
接口与模块内部变量的绑定是动态的
export let a = 1;
setTimeout(() => {
a = 2;
}, 100); // 100毫秒后 a=2
2.import
import导入接口,大括号中的名称必须和导出的名称相同
// 可以使用 as 重命名,cx 不再可见
import {a, b, cx as cy} from './001';
可以用星号(*)指定一个对象,所有导出的接口都挂载到这个对象
import * as whole from './001';
也可以只执行不导入
import './001';
3.export default
export default指定默认导出接口,后面跟的是一个表达式,不能再是声明语句
let a = 1;
export default a;
// 此时函数是表达式,不是声明
export default function () {}
// 此时类是表达式,不是声明
export default class {}
import可以指定任意名字接收
// 001.mjs
// 一个模块只能有一个默认导出接口,也就是只能使用一次
let a = 1;
export default a;
export let b = 2;
// 002.mjs
// 接收默认导出的变量必须在最前面
import ax, * as x from './001';
console.log(ax, x, x.default); // 1 [Module] { b: 2, default: 1 } 1
根据以上输出,可以看出export default的本质
// export default
export default a;
// 等价于
export {a as default}
// 貌似可以在大括号中用 default 接收,但是因为 default 是关键字,可以用 as 重命名
import ax, {default as ay, b} from './001';
console.log(ax, ay, b); // 1 1 2
export与import的复合写法
// 001.mjs
const a = 1;
export default a;
export const b = 2;
// 002.mjs
// b并没有被导入到当前模块
export {b} from './001';
// 002.mjs
// 导出001.mjs中的默认接口
// 不能这样 export a from './001';
export {default} from './001';
// 002.mjs
// 重命名,此时 b 是默认导出接口,只能有一个默认接口
export {default as ax, b as default} from './001';
// 002.mjs
// 整体导出,不包括001.mjs中的默认接口
// 不能这样 import * as x from './001';
export * from './001';
循环加载
// a.mjs
import {b} from './b';
console.log('a.mjs');
console.log(b);
export let a = 'a';
// b.mjs
import {a} from './a';
console.log('b.mjs');
console.log(a);
export let b = 'b';
执行 a.mjs ,根据多次调试,个人理解的执行过程如下(可能有偏差):
一、编译阶段
编译入口模块
- 1).检测语法
- 2).确定模块依赖关系、导入导出的接口
- 3).编译依赖的模块:如果已编译,则不再去编译(避免循环编译),否则,按此过程递归编译
编译阶段结束后,开始进行执行阶段。
二、执行阶段
- 1).首先执行入口模块的
import语句 - 2).检测依赖的模块是否存在、该模块是否导出了相关接口
- 3).执行依赖的模块:如果该模块在执行状态,则不去执行(避免循环执行),否则,按此过程递归执行
- 4).依赖的模块执行完,接着执行本模块
参考
1.http://es6.ruanyifeng.com/#docs/module
2.http://es6.ruanyifeng.com/#docs/module-loader
Reactions are currently unavailable