module
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,ES规范成为浏览器和服务器通用的模块解决方案。
- ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。
比如,CommonJS 模块就是对象,输入时必须查找对象属性。而ES6 模块不是对象。
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
// CommonJS模块
let { stat, exists, readfile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取 3 个方法。这种加载称为“运行时加载”
,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。
ES6 模块是通过export命令显式指定输出的代码,再通过import命令输入。
import { stat, exists, readFile } from 'fs';
import { stat, exists, readFile } from 'fs';
上面代码的实质是从fs模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或静态加载
,即 ES 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。
编译时加载,使得静态分析成为可能。有了它,就能进一步拓宽 JavaScript 的语法,比如引入宏(macro)和类型检验(type system)这些只能靠静态分析实现的功能。
import()
- import命令会被 JavaScript 引擎静态分析,先于模块内的其他语句执行。也就是说,import和export命令只能在模块的顶层,不能在代码块之中。
- ES2020提案 引入
import(specifier)
函数,支持动态加载模块。它是运行时执行(异步)。import函数的参数specifier,指定所要加载的模块的位置。import命令能够接受什么参数,import()函数就能接受什么参数,两者区别主要是后者为动态加载。
export 命令
模块功能主要由两个命令构成:export和import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
- 一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。使用
export
关键字可以导出内部变量。
// 第一种
export let variable1 = 1;
export let variable2 = 2;
// 第二种
let variable1 = 1;
let variable2 = 2;
export { variable1,variable2};
// 第一种
export let variable1 = 1;
export let variable2 = 2;
// 第二种
let variable1 = 1;
let variable2 = 2;
export { variable1,variable2};
- export输出的变量就是本来的名字,可以使用
as
关键字重命名。
let variable1 = 1;
let variable2 = 2;
export {
v1 as variable1,
v2 as variable1,
};
let variable1 = 1;
let variable2 = 2;
export {
v1 as variable1,
v2 as variable1,
};
- export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错。
function foo() {
export default 'bar' // SyntaxError
}
foo()
function foo() {
export default 'bar' // SyntaxError
}
foo()
- export命令规定的是对外的接口,接口名与模块内部变量之间,必须建立一 一对应的关系。
可以理解为导出文件中的一块区域,不能直接导出单个变量。只能是:
①
export + 完整的申明
(var、const、let、function、class)②
export + { 变量名, 变量名,... }
③
export + 模块集合
export let name1; // export + 完整的申明
export { name1, /* …, */ nameN }; // export + { 变量名, 变量名,... }
export * from "module-name"; // 导出模块合集
export * as module1 from "module-name"; // 导出模块合集 重新命名
export { name1, /* …, */ nameN } from "module-name"; // 导出模块合集 中的一部分
export let name1; // export + 完整的申明
export { name1, /* …, */ nameN }; // export + { 变量名, 变量名,... }
export * from "module-name"; // 导出模块合集
export * as module1 from "module-name"; // 导出模块合集 重新命名
export { name1, /* …, */ nameN } from "module-name"; // 导出模块合集 中的一部分
注意
export 语句输出的接口,与其对应的值是动态绑定关系
,即通过该接口,可以取到模块内部实时的值。
export var foo = 1;
setTimeout(() => foo = 2, 1000);
export var foo = 1;
setTimeout(() => foo = 2, 1000);
上面代码输出变量foo,值为1,1000 毫秒之后变成2。这一点与 CommonJS 规范完全不同。CommonJS 模块输出的是值的缓存,不存在动态更新,原因是ES是引用、CommonJS是拷贝,拷贝时是什么就是什么。
import 命令
使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块。
import "module-name";
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";
import defaultExport from "module-name";
import * as name from "module-name";
import { export1 } from "module-name";
import { export1 as alias1 } from "module-name";
import { default as alias } from "module-name";
import { export1, export2 } from "module-name";
import { export1, export2 as alias2, /* … */ } from "module-name";
import { "string name" as alias } from "module-name";
import defaultExport, { export1, /* … */ } from "module-name";
import defaultExport, * as name from "module-name";
- import命令要使用as关键字,将输入的变量重命名。
- import命令输入的变量都是只读的,因为它的本质是输入接口。不允许改写接口。
- import后面的from指定模块文件的位置,可以是相对路径,也可以是绝对路径,.js后缀可以省略。
如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
- import命令具有提升效果,会提升到整个模块的头部,首先执行(可以出现在任何位置,只要处于模块顶层,处于块级作用域报错),因为import命令是编译阶段执行的。
- 如果多次重复执行同一句import语句,那么只会执行一次,而不会执行多次。
export default 命令
- export default命令,为模块指定默认输出。一个模块只能有一个默认输出。
- export default就是输出一个叫做default的变量或方法(所以它后面不能跟变量声明语句),然后系统允许你为它取任意名字。