在模組化被寫入 ECMAScript 標準之前, 已經存在各種模組化的實現方式和對應的語法, 例如 AMD, CMD, commonJS 等. 本文只討論 ES6 標準下的模組化語法.
模組化的必要性
當我們希望全域性作用域更加乾淨,而不是到處都有命名衝突之類的問題;
當我們希望一段程式碼擁有自己的作用域, 而且不要被其他程式碼所汙染;
當我們希望自己的程式更加井然有序;
當......
這也是 ES6 希望解決的問題, 所以將模組化定為了標準.
模組的概念
模組是一段JavaScript程式碼, 這段程式碼會自動執行、而且是在嚴格模式下、並且無法退出執行。
模組的特點
- 模組內定義的變數不會被自動新增到全域性作用域
- 由於上面的原因, 模組要向外面暴露一些自己的資料, 這些資料可以被外界訪問到
- 一個模組可以從另外一個模組中匯入資料(即可以使用其他模組的資料)
- 模組頂層的
this
是undefined
, 並不是window
或global
模組和指令碼的區別
模組和指令碼初看起來很相似, 他們都可以存在一個單獨的檔案中, 都可以被其他模組(指令碼)引入, 也可以引入其他模組(指令碼)的資料, 似乎很難說出他們之間的區別.
但是其實他們的區別非常明顯, 用一句話就可以概括: 我們可以按需匯入模組中的資料, 但是不能按需匯入指令碼中的資料。
對於指令碼,我們一旦匯入了它, 就會將指令碼中的所有資料全都匯入到了全域性作用域中, 這樣看起來還是挺亂的。
而對於模組, 則可以只匯入我們需要的資料。雖然一個模組可能會暴露出很多的變數、函式和物件, 但我們可以只把需要的那一部分匯入進來使用。
從其他模組匯入資料和將模組中的資料暴露出去給其他模組的語法 export
和 import
將模組中的資料暴露(匯出)給其他模組 export
將模組中的資料暴露給其他模組使用的語法有兩種, 分別如下:
- 先定義一個值,然後將其暴露出去
// 定義一個變數
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種,分別是:
- 定義的時候就暴露
- 先定義後暴露
- 將這個值用 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';
複製程式碼
將從其他模組匯入的資料暴露出去
有時候需要將從其他模組匯入的某些資料再暴露出去, 這有兩種語法
- 先匯入, 再暴露
import { name } from './a.js'; // 匯入
export {name}; // 暴露
複製程式碼
- 一行程式碼之內同時匯入和暴露
export { name } from './a.js';
複製程式碼
也可以重新命名後再暴露出去
export { name as lastName } from './a.js';
複製程式碼
#完. 文章或有疏漏之處, 歡迎指正.