### 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`方法,區別主要是前者是異步加載,后者是同步加載。