### 1. ES6 模块不是对象,而是通过`export`命令显式指定输出的代码,再通过`import`命令输入。
~~~javascript
// ES6模块
import { stat, exists, readFile } from 'fs';
~~~
上面代码的实质是从`fs`模块加载 3 个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。
### 2. 严格模式
* 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`)
### 3.export 命令
* 模块功能主要由两个命令构成:`export`和`import`。`export`命令用于规定模块的对外接口,`import`命令用于输入其他模块提供的功能。
`export`的写法,除了像上面这样,还有另外一种。
~~~javascript
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
~~~
上面代码在`export`命令后面,使用大括号指定所要输出的一组变量。它与前一种写法(直接放置在`var`语句前)是等价的,但是应该优先考虑使用这种写法。因为这样就可以在脚本尾部,一眼看清楚输出了哪些变量。
`export`命令除了输出变量,还可以输出函数或类(class)。
~~~javascript
export function multiply(x, y) {
return x * y;
};
~~~
上面代码使用`as`关键字,重命名了函数`v1`和`v2`的对外接口。重命名后,`v2`可以用不同的名字输出两次。
上面代码对外输出一个函数`multiply`。
通常情况下,`export`输出的变量就是本来的名字,但是可以使用`as`关键字重命名。
~~~javascript
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
~~~
### 3.import命令
* 使用`export`命令定义了模块的对外接口以后,其他 JS 文件就可以通过`import`命令加载这个模块。
~~~javascript
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
~~~
* 上面代码的`import`命令,用于加载`profile.js`文件,并从中输入变量。`import`命令接受一对大括号,里面指定要从其他模块导入的变量名。大括号里面的变量名,必须与被导入模块(`profile.js`)对外接口的名称相同。
如果想为输入的变量重新取一个名字,`import`命令要使用`as`关键字,将输入的变量重命名。
~~~javascript
import { lastName as surname } from './profile.js';
~~~
* `import`命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口。
* `import`后面的`from`指定模块文件的位置,可以是相对路径,也可以是绝对路径,`.js`后缀可以省略。如果只是模块名,不带有路径,那么必须有配置文件,告诉 JavaScript 引擎该模块的位置。
~~~javascript
import {myMethod} from 'util';
~~~
上面代码中,`util`是模块文件名,由于不带有路径,必须通过配置,告诉引擎怎么取到这个模块
### 5.整体加载
除了指定加载某个输出值,还可以使用整体加载,即用星号(`*`)指定一个对象,所有输出值都加载在这个对象上面。
下面是一个`circle.js`文件,它输出两个方法`area`和`circumference`。
~~~javascript
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
~~~
现在,加载这个模块。
~~~javascript
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
~~~
上面写法是逐一指定要加载的方法,整体加载的写法如下。
~~~javascript
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
~~~
### 6.export default命令
为了给用户提供方便,让他们不用阅读文档就能加载模块,就要用到`export default`命令,为模块指定默认输出。
~~~javascript
// export-default.js
export default function () {
console.log('foo');
}
~~~
上面代码是一个模块文件`export-default.js`,它的默认输出是一个函数。
其他模块加载该模块时,`import`命令可以为该匿名函数指定任意名字。
~~~javascript
// import-default.js
import customName from './export-default';
customName(); // 'foo'
~~~
上面代码的`import`命令,可以用任意名称指向`export-default.js`输出的方法,这时就不需要知道原模块输出的函数名。需要注意的是,这时`import`命令后面,不使用大括号。
`export default`命令用在非匿名函数前,也是可以的。
~~~javascript
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;
~~~
上面代码中,`foo`函数的函数名`foo`,在模块外部是无效的。加载的时候,视同匿名函数加载。
下面比较一下默认输出和正常输出。
~~~javascript
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入
~~~
上面代码的两组写法,第一组是使用`export default`时,对应的`import`语句不需要使用大括号;第二组是不使用`export default`时,对应的`import`语句需要使用大括号。
`export default`命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此`export default`命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应`export default`命令。
### 7.import()方法
因为`require`是运行时加载模块,`import`命令无法取代`require`的动态加载功能。
~~~javascript
const path = './' + fileName;
const myModual = require(path);
~~~
上面的语句就是动态加载,`require`到底加载哪一个模块,只有运行时才知道。`import`命令做不到这一点。
因此,有一个[提案](https://github.com/tc39/proposal-dynamic-import),建议引入`import()`函数,完成动态加载。
~~~javascript
import(specifier)
~~~
上面代码中,`import`函数的参数`specifier`,指定所要加载的模块的位置。`import`命令能够接受什么参数,`import()`函数就能接受什么参数,两者区别主要是后者为动态加载。
`import()`返回一个 Promise 对象。下面是一个例子。
~~~javascript
const main = document.querySelector('main');
import(`./section-modules/${someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
~~~
`import()`函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块。另外,`import()`函数与所加载的模块没有静态连接关系,这点也是与`import`语句不相同。`import()`类似于 Node 的`require`方法,区别主要是前者是异步加载,后者是同步加载。