深入理解ES6--13.用模組封裝程式碼

你聽___發表於2018-05-08

深入理解ES6--13.用模組封裝程式碼

主要知識點:什麼是模組、模組中的匯出、模組中的匯入

模組的知識點

1. 什麼是模組?

模組(Modules ) 是使用不同方式載入的 JS 檔案(與 JS 原先的指令碼載入方式相對) 。這種不同模式很有必要,因為它與指令碼(script ) 有大大不同的語義:

  1. 模組程式碼自動執行在嚴格模式下,並且沒有任何辦法跳出嚴格模式;
  2. 在模組的頂級作用域建立的變數,不會被自動新增到共享的全域性作用域,它們只會在模組頂級作用域的內部存在;
  3. 模組頂級作用域的 this 值為 undefined
  4. 模組不允許在程式碼中使用 HTML 風格的註釋(這是 JS 來自於早期瀏覽器的歷史遺留特性) ;
  5. 對於需要讓模組外部程式碼訪問的內容,模組必須匯出它們;
  6. 允許模組從其他模組匯入繫結;

1.1 基本的匯出

可以使用 export 關鍵字將已釋出程式碼部分公開給其他模組。最簡單方法就是將 export放置在任意變數、函式或類宣告之前:

// 匯出資料
export var color = "red";
export let name = "Nicholas";
export const magicNumber = 7;
// 匯出函式
export function sum(num1, num2) {
		return num1 + num1;
} 
// 匯出類
export class Rectangle {
	constructor(length, width) {
	this.length = length;
	this.width = width;
	}
} 
// 此函式為模組私有
function subtract(num1, num2) {
		return num1 - num2;
} 
// 定義一個函式……
function multiply(num1, num2) {
		return num1 * num2;
} 
// ……匯出一個函式引用
export { multiply };
複製程式碼

此例中有幾點需要注意。首先,除了 export 關鍵字之外,每個宣告都與正常形式完全一樣。每個被匯出的函式或類都有名稱,這是因為匯出的函式宣告與類宣告必須要有名稱。你不能使用這種語法來匯出匿名函式或匿名類,除非使用了 default 關鍵字 。其次,細看一下 multiply() 函式,它並沒有在定義時被匯出。這是因為你不僅能匯出宣告,還可以匯出引用(即程式碼最後一行) 。最後請注意,此例並未匯出subtract() 函式。此函式在模組外部不可訪問,因為任意沒有被顯式匯出的變數、函式或類都會在模組內保持私有。

1.2 基本的匯入

一旦你有了包含匯出的模組,就能在其他模組內使用 import 關鍵字來訪問已被匯出的功能。 import 語句有兩個部分,一是需要匯入的識別符號,二是需匯入的識別符號的來源模組。此處是匯入語句的基本形式:

import { identifier1, identifier2 } from "./example.js";
複製程式碼

import 之後的花括號指明瞭從給定模組匯入對應的繫結, from 關鍵字則指明瞭需要匯入的模組。模組由一個表示模組路徑的字串(被稱為模組說明符, module specifier ) 來指定。

當從模組匯入了一個繫結時,該繫結表現得就像使用了 const 的定義。這意味著你不能再定義另一個同名變數(包括匯入另一個同名繫結) ,也不能在對應的 import 語句之前使用此識別符號(也就是要受暫時性死區限制) ,更不能修改它的值。

匯入單個繫結

實現匯入單個繫結時,僅僅只需要使用一個識別符號:

// 單個匯入
import { sum } from "./example.js";
console.log(sum(1, 2)); // 3
sum = 1; // 出錯
複製程式碼

對於已匯入的繫結再重新賦值,則會導致錯誤。

匯入多個繫結

如果你想從 example 模組匯入多個繫結,你可以像下面這樣顯式的列出它們:

// 多個匯入
import { sum, multiply, magicNumber } from "./example.js";
console.log(sum(1, magicNumber)); // 8
console.log(multiply(1, 2)); // 2
複製程式碼

此處從 example 模組匯入了三個繫結: summultiplymagicNumber

完全匯入一個模組

還有一種特殊情況,即允許你將整個模組當作單一物件進行匯入,該模組的所有匯出都會作為物件的屬性存在。例如:

// 完全匯入
import * as example from "./example.js";
console.log(example.sum(1,
example.magicNumber)); // 8
console.log(example.multiply(1, 2)); // 2
複製程式碼

在此程式碼中, example.js 中所有匯出的繫結都被載入到一個名為 example 的物件中,具名匯出( sum() 函式、 multiple() 函式與 magicNumber ) 都成為 example可用屬性。這種匯入格式被稱為名稱空間匯入(namespace import ) ,這是因為該 example 物件並不存在於 example.js 檔案中,而是作為一個名稱空間物件被建立使用,其中包含了example.js 的所有匯出成員。

需要注意的是:無論對同一個模組使用了多少次 import 語句,該模組都只會被執行一次。在匯出模組的程式碼執行之後,已被例項化的模組就被保留在記憶體中,並隨時都能被其他 import 所引用

匯入繫結無法修改原始值

ES6 的 import 語句為變數、函式與類建立了只讀繫結,而不像普通變數那樣簡單引用了原始繫結。儘管匯入繫結的模組無法修改繫結的值,但是可以在匯出模組中對原始值做出修改,匯入繫結會自動反映出修改的變化,例如:

匯出模組:

export var name = "Nicholas";
export function setName(newName) {
	name = newName;
}
複製程式碼

匯入模組:

import { name, setName } from "./example.js";
console.log(name); // "Nicholas"
setName("Greg");
console.log(name); // "Greg"
name = "Nicholas"; // error
複製程式碼

呼叫 setName("Greg") 會回到匯出 setName() 的模組內部,並在那裡執行,從而將 name 設定為 "Greg" 。注意這個變化會自動反映到所匯入的 name 繫結上。

1.3 重新命名的匯出與匯入

在匯出模組中進行重新命名

如果想用不同的名稱來匯出,可以使用 as 關鍵字來定義新的名稱:

function sum(num1, num2) {
	return num1 + num2;
} 
export { sum as add };
複製程式碼

此處的 sum() 函式被作為 add() 匯出,前者是本地名稱(local name ) ,後者則是匯出名稱(exported name ) 。這意味著當另一個模組要匯入此函式時,它必須改用 add 這個名稱:

import {add} from './example.js'
複製程式碼

在匯入時重新命名

在匯入時同樣可以使用 as 關鍵字進行重新命名:

import { add as sum } from './example.js'
console.log(typeof add); // "undefined"
console.log(sum(1, 2)); // 3
複製程式碼

此程式碼匯入了add() 函式,並使用了匯入名稱(import name ) 將其重新命名為 sum()(本地名稱) 。這意味著在此模組中並不存在名為 add 的識別符號。

2. 模組的預設值

模組的預設值( default value ) 是使用 default 關鍵字所指定的單個變數、函式或類,而你在每個模組中只能設定一個預設匯出,將 default 關鍵字用於多個匯出會是語法錯誤。

2.1 匯出預設值

匯出預設值一共有三種形式:

  1. 不使用識別符號
export default function(num1,num2){
	return num1+num2;
}
複製程式碼

此模組將一個函式作為預設值進行了匯出, default 關鍵字標明瞭這是一個預設匯出,此函式並不需要有名稱。

  1. 使用識別符號
function sum(num1, num2) {
	return num1 + num2;
} 
export default sum;
複製程式碼
  1. 使用重新命名語法
function sum(num1, num2) {
	return num1 + num2;
} 
export {sum as default};
複製程式碼
  1. 既匯出了預設值,又匯出非預設值
export let color = 'red';
export default function(num1,num2){
	return num1+num2;
}
複製程式碼

2.2 匯入預設值

只匯入預設值

import sum from './example.js';
console.log(sum(1,2));
複製程式碼

這個匯入語句從 example.js 模組匯入了其預設值。與之前在非預設的匯入中看到的不同,注意此處並未使用花括號。本地名稱 sum 被用於代表目標模組所預設匯出的函式。

既匯入預設值,又匯入非預設值

import sum, { color } from './example.js';
console.log(sum(1,2));
console.log(color);
複製程式碼

逗號將預設的本地名稱與非預設的名稱分隔開,非預設值仍舊被花括號所包裹。要記住在 import 語句中預設名稱必須位於非預設名稱之前。

對匯入預設值重新命名

import {default as sum, color} from './example.js'

console.log(sum(1,2));
console.log(color);
複製程式碼

在此程式碼中,預設的匯出( default ) 被重新命名為 sum ,並且附加的 color 匯出也被一併匯入了。

2.3 對已匯入的內容再匯出

如果在當前模組中對已匯入的內容在匯出:

export {sum} from './example.js';
複製程式碼

這種形式的 export 會進入指定模組檢視 sum 的定義,隨後將其匯出。在匯出時同樣也可以進行重新命名:

export { sum as add } from './example.js'
複製程式碼

如果想將另一個模組中的所有值完全匯出,可以使用 * 號模式:

export * from './example.js';
複製程式碼

用完全匯出,就可以匯出目標模組的預設值及其所有具名匯出,但這可能影響你從當前模 塊所能匯出的值。例如,假設 example.js 具有一個預設匯出,當你使用這種語法時,你就無法為當前模組另外再定義一個預設匯出。

2.4 無繫結的匯入

有些模組也許沒有進行任何匯出,相反只是修改全域性作用域的物件。儘管這種模組的頂級變數、函式或類最終並不會自動被加入全域性作用域,但這並不意味著該模組無法訪問全域性作用域。諸如 ArrayObject 之類的內建物件的共享定義在模組內部是可訪問的,並且對於這些物件的修改會反映到其他模組中。

3. 總結

  1. ES6 為 JS 語言新增了模組,作為打包與封裝功能的方式。模組的行為異於指令碼,它們不會用自身頂級作用域的變數、函式或類去修改全域性作用域,而模組的 this 值為 undefined
  2. 可以在模組中使用 export 關鍵字匯出,變數、函式與類都可以,並且每個模組允許存在一個預設匯出。在匯出之後,另一個模組就能匯入該模組所匯出的一個或多個匯出值。這些匯入的名稱就像是被 const 所定義的,會被當作塊級繫結,並且不允在同一模組內重複宣告;
  3. 由於模組必須用與指令碼不同的方式執行,瀏覽器就引入了 <script type="module"> ,以表示資原始檔或內聯程式碼需要作為模組來執行。使用

相關文章