介绍
ES6 之前用于 JavaScript 的模块加载方案,是一些社区提供的,主要有 CommonJS
和 AMD
两种,前者用于服务器,后者用于浏览器。
ES6 提供了模块的实现,使用 export
命令对外暴露接口,使用 import
命令输入其他模块暴露的接口。
// CommonJS 模块
let {
stat,
exists,
readFire
} = require('fs');
// ES6 模块
import {
stat,
exists,
readFire
} = from 'fs';
严格模式
ES6 模块自动采用严格模式,无论模块头部是否有 "use strict"
。
严格模式有以下限制:
- 变量必须声明后再使用
- 函数的参数不能有同名属性,否则报错
- 不能使用
with
语句 - 不能对只读属性赋值,否则报错
- 不能使用前缀 0 表示八进制数,否则报错
- 不能删除不可删除的属性,否则报错
- 不能删除变量
delete prop
,会报错,只能删除属性delete * global[prop]
eval
不会在它的外层作用域引入变量eval
和arguments
不能被重新赋值arguments
不会自动反映函数参数的变化- 不能使用
arguments.callee
- 不能使用
arguments.caller
- 禁止
this
指向全局对象 - 不能使用
fn.caller
和fn.arguments
获取函数调用的堆栈 - 增加了保留字(比如
protected
、static
和interface
)
特别是,ES6 中顶层 this
指向 undefined
,即不应该在顶层代码使用 this
。
export 命令
使用 export
向模块外暴露接口,可以是方法,也可以是变量。
// 1. 变量
export let a = 'leo';
export let b = 100;
// 还可以
let a = 'leo';
let b = 100;
export {
a,
b
};
// 2. 方法
export function f(a, b) {
return a * b;
}
// 还可以
function f1() {
...
}
function f2() {
...
}
export {
a1 as f1,
a2 as f2
}
可以使用 as
重命名函数的对外接口。
特别注意:export
暴露的必须是接口,不能是值。
// 错误
export 1; // 报错
let a = 1;
export a; // 报错
// 正确
export let a = 1; // 正确
let a = 1;
export {
a
}; // 正确
let a = 1;
export {
a as b
}; // 正确
暴露方法也是一样:
// 错误
function f() {
...
};
export f;
// 正确
export function f() {
...
};
function f() {
...
};
export {
f
};
16.4 import 命令
加载 export
暴露的接口,输出为变量。
import {
a,
b
} from '/a.js';
function f() {
return a + b;
}
import
后大括号指定变量名,需要与 export
的模块暴露的名称一致。
也可以使用 as
为输入的变量重命名。
import {
a as leo
} from './a.js';
import
不能直接修改输入变量的值,因为输入变量只读只是个接口,但是如果是个对象,可以修改它的属性。
// 错误
import {
a
} from './f.js';
a = {}; // 报错
// 正确
a.foo = 'leo'; // 不报错
import
命令具有提升效果,会提升到整个模块头部最先执行,且多次执行相同 import
只会执行一次。
模块的整体加载
当一个模块暴露多个方法和变量,引用时可以用 *
整体加载。
// a.js
export function f() {
...
}
export function g() {
...
}
// b.js
import * as obj from '/a.js';
console.log(obj.f());
console.log(obj.g());
但是,不允许运行时改变:
import * as obj from '/a.js';
// 不允许
obj.a = 'leo';
obj.b = function() {
...
};
export default 命令
使用 export default
命令,为模块指定默认输出,引用的时候直接指定任意名称即可。
// a.js
export default function() {
console.log('leo')
};
// b.js
import leo from './a.js';
leo(); // 'leo'
export default
暴露有函数名的函数时,在调用时相当于匿名函数。
// a.js
export default function f() {
console.log('leo')
};
// 或者
function f() {
console.log('leo')
};
export default f;
// b.js
import leo from './a.js';
export default
其实是输出一个名字叫 default
的变量,所以后面不能跟变量赋值语句。
// 正确
export let a = 1;
let a = 1;
export default a;
// 错误
export default
let a = 1;
export default
命令的本质是将后面的值,赋给 default
变量,所以可以直接将一个值写在 export default
之后。
// 正确
export detault 1;
// 错误
export 1;
export 和 import 复合写法
常常在先输入后输出同一个模块使用,即转发接口,将两者写在一起。
export {
a,
b
}
from './leo.js';
// 理解为
import {
a,
b
} from './leo.js';
export {
a,
b
}
常见的写法还有:
// 接口改名
export {
a as b
}
from './leo.js';
// 整体输出
export * from './leo.js';
// 默认接口改名
export {
default as a
}
from './leo.js';
常常用在模块继承。
在浏览器中使用
ES6 中,可以在浏览器使用 <script>
标签,需要加入 type="module"
属性,并且这些都是异步加载,避免浏览器阻塞,即等到整个页面渲染完,再执行模块脚本,等同于打开了 <script>
标签的 defer
属性。
<script type="module" src="./a.js"></script>
另外,ES6 模块也可以内嵌到网页,语法与外部加载脚本一致:
<script type="module">
import a from './a.js';
</script>
在 Node 中使用
安装最新版本的 Node,在 Node 13.2.0 之后,不需要使用 --experimental-modules
运行参数。
两种方法:
- 在
package.json
中添加"type": "module"
。使用这种方法,所有的.js
和.mjs
文件都被解释为 ES 模块。如果想要将其解释为 CommonJS,则必须使用.cjs
扩展名。 - 不在
package.json
中设置类型,.js
文件会被当作 CommonJS 对待,但可以使用扩展名.mjs
来让 Node 知道它是一个 ES6 模块。
总结
- 代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
- 模块脚本自动采用严格模式,不管有没有声明
use strict
。 - 模块之中,可以使用
import
命令加载其他模块(.js
后缀不可省略,需要提供绝对 UR
L 或相对 UR
L),也可以使用export
命令输出对外接口。 - 模块之中,顶层的
this
关键字返回undefined
,而不是指向window
。也就是说,在模块顶层使用this
关键字,是无意义的。 - 同一个模块如果加载多次,将只执行一次。