介绍
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后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。 - 模块之中,顶层的
this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。 - 同一个模块如果加载多次,将只执行一次。