Skip to content

ES6 Module #18

Description

@hashdrinker

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions