ES6的模組化語法

mynull發表於2019-03-31

在模組化被寫入 ECMAScript 標準之前, 已經存在各種模組化的實現方式和對應的語法, 例如 AMD, CMD, commonJS 等. 本文只討論 ES6 標準下的模組化語法.

模組化的必要性

當我們希望全域性作用域更加乾淨,而不是到處都有命名衝突之類的問題;

當我們希望一段程式碼擁有自己的作用域, 而且不要被其他程式碼所汙染;

當我們希望自己的程式更加井然有序;

當......

這也是 ES6 希望解決的問題, 所以將模組化定為了標準.

模組的概念

模組是一段JavaScript程式碼, 這段程式碼會自動執行、而且是在嚴格模式下、並且無法退出執行。

模組的特點

  • 模組內定義的變數不會被自動新增到全域性作用域
  • 由於上面的原因, 模組要向外面暴露一些自己的資料, 這些資料可以被外界訪問到
  • 一個模組可以從另外一個模組中匯入資料(即可以使用其他模組的資料)
  • 模組頂層的 thisundefined, 並不是 windowglobal

模組和指令碼的區別

模組和指令碼初看起來很相似, 他們都可以存在一個單獨的檔案中, 都可以被其他模組(指令碼)引入, 也可以引入其他模組(指令碼)的資料, 似乎很難說出他們之間的區別.

但是其實他們的區別非常明顯, 用一句話就可以概括: 我們可以按需匯入模組中的資料, 但是不能按需匯入指令碼中的資料。

對於指令碼,我們一旦匯入了它, 就會將指令碼中的所有資料全都匯入到了全域性作用域中, 這樣看起來還是挺亂的。

而對於模組, 則可以只匯入我們需要的資料。雖然一個模組可能會暴露出很多的變數、函式和物件, 但我們可以只把需要的那一部分匯入進來使用。

從其他模組匯入資料和將模組中的資料暴露出去給其他模組的語法 exportimport

將模組中的資料暴露(匯出)給其他模組 export

將模組中的資料暴露給其他模組使用的語法有兩種, 分別如下:

  1. 先定義一個值,然後將其暴露出去

// 定義一個變數
let name = 'doug';
// 暴露這個變數
export name;

// 定義一個函式
function say(){
    console.log('hello ' + name);
}
// 暴露這個函式
export say;

// 定義一個類
class Student {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}
// 暴露這個類
export Student;

// 定義一個函式,而且不把它暴露出去
function privateFunc(){
    console.log('我是私有成員,沒有被暴露出去,所以別人沒有辦法訪問到我');
}
複製程式碼

上面的操作可以分為兩個過程, 先定義、再暴露。其實可以簡化為一句程式碼: 2. 在定義變數的同時暴露資料, 只要在宣告符之前加上 export 就可以.

// 定義一個變數並暴露
export let name = 'doug';

// 定義一個函式並暴露
export function say(){
    console.log('hello ' + name);
}

// 定義一個類並暴露
export class Student {
    constructor(name, age){
        this.name = name;
        this.age = age;
    }
}

// 定義一個函式,並不把它暴露出去, 其他模組從外部無法訪問這個函式
function privateFunc(){
    console.log('我是私有成員,沒有被暴露出去,所以別人沒有辦法訪問到我');
}
複製程式碼

注意, 不暴露出去的資料是從外部訪問不到的, 例如上面例子中的 privateFunc 函式.

從別的模組匯入資料 import

從別的模組得到資料以供自己使用, 要用 import 關鍵字。在匯入之前, 要先明確兩個問題: 一是從哪個模組匯入? 二是想匯入模組中的什麼資料?

在明確從哪匯入和匯入什麼之後,就可以按照下面的語法來寫了:

import { 資料1, 資料2, ... , 資料n } from 模組名
複製程式碼

例如,從 a 模組中匯入 name 變數 和 say 函式:

import { name, say } from './a.js';
複製程式碼

這樣就把 name 和 say 從 a 模組匯入進本模組中了,現在就可以使用它們了, 例如輸出 name 的值, 或者呼叫 say 這個函式:

console.log(name);

say();
複製程式碼

關於匯入的注意事項

匯入的資料是沒有辦法直接改變的。比如,我們無法直接給 name 重新賦值, 這樣會報錯。

import { name } from './a.js';

name = 'new name';  // Error 錯誤
複製程式碼

但是可以間接的修改這個變數的值. 假如在模組 a 中定義了一個可以修改 name 值的函式, 則可以通過呼叫這個函式來修改 name 的值了. a 模組中的內容:

... 包括 name 在內的其他資料

function updateName(newName){
    name = newName;
}
複製程式碼

匯入 a模組的 updateName 函式來修改 name 的值:

import { name, updateName } from './a.js';

updateName('new Name'); // name的值已經被修改為了 "new name"
複製程式碼

一次性匯入一個模組暴露出來的所有資料

當想用一行程式碼匯入一個模組暴露出來的所有資料時, 用上面的方法可能比較困難, 因為一個模組暴露出來的方法可能很多, 把每個變數名都寫出來的方法就不夠靈活了.

這個時候可以用名稱空間匯入的方法, 即將一個模組暴露出來的所有的資料掛載到一個物件上,通過這個物件來呼叫這些資料, 例如:

import * as aObj from './a.js'; // a 模組所有暴露出的資料都掛到了 aObj 上

// 通過 aObj 來訪問和呼叫 a 模組中的變數和方法
console.log(aObj.name);
aObj.say();
複製程式碼

使用 as 對匯入和要暴露出去的資料重新命名

如果想將資料以別的名稱暴露出去, 可以使用 as 關鍵字, 語法為: export { 原名稱 as 新名稱 } .

這樣在其他模組中就可以用這個新的名稱來匯入這個資料了, 例如將函式 say 以 speak 為名稱暴露出去:

export {say as speak};
複製程式碼

那麼其他模組就要用 speak 來匯入這個函式了: import { speak } from './a.js'

在匯入一個資料時, 也可以對它重新命名, 並使用重新命名之後的名稱來訪問它, 例如將它命名為 Howling, 之後就可以使用 howling 來訪問這個函式了:

import { speak as howling} from './a.js';  // 匯入時重新命名

howling(); // 用新名字呼叫這個函式
複製程式碼

將一個值作為預設值暴露出去 default

每個模組可以將一個變數、函式或者類作為預設值暴露出去.

將一個值作為預設值暴露出去的語法有3種,分別是:

  1. 定義的時候就暴露
  2. 先定義後暴露
  3. 將這個值用 as 命名為 default 後暴露
// 1. 定義的時候就暴露
export default function(){
    console.log('方法1暴露函式為預設值');
}

// 2. 先定義後暴露
let name = 'doug';
export default name;

// 3. 將這個值用 as 命名為 default 暴露
class Student{ // ... }
export {Student as default};
複製程式碼

匯入其他模組暴露出的預設值

匯入預設值的語法和匯入非預設值的語法略有不同. 匯入一個模組暴露出的預設值, 並不用 {} 來包裹, 而且不用使用 as 關鍵字就可以將這個預設值重新命名: 例如匯入上面例子中的 name, 並將其重新命名為 familyName

import familyName from './a.js';
複製程式碼

同時匯入預設值和非預設值的語法

在一行語句中同時匯入預設值和非預設值的語法非常簡單, 就是將這兩種匯入的方式結合起來, 非預設值用{}括起來, 而預設值不用. 語法為:

import 預設值 { 非預設值 } from './a.js';
複製程式碼

將從其他模組匯入的資料暴露出去

有時候需要將從其他模組匯入的某些資料再暴露出去, 這有兩種語法

  1. 先匯入, 再暴露
import { name } from './a.js';   // 匯入

export {name};   // 暴露
複製程式碼
  1. 一行程式碼之內同時匯入和暴露
export { name } from './a.js';
複製程式碼

也可以重新命名後再暴露出去

export { name as lastName } from './a.js';
複製程式碼

#完. 文章或有疏漏之處, 歡迎指正.

在模組化被寫入 ECMAScript 標準之前, 已經存在各種模組化的實現方式和對應的語法, 例如 AMD, CMD, commonJS 等. 後面的文章會對這些標準進行對比.