ES6之路之模組詳解

leexiaoran發表於2019-02-16

歡迎訪問個人站點

簡介

何為模組

一個模組只不過是一個寫在檔案中的 JavaScript 程式碼塊。

模組中的函式或變數不可用,除非模組檔案匯出它們。

簡單地說,這些模組可以幫助你在你的模組中編寫程式碼,並且只公開應該被你的程式碼的其他部分訪問的程式碼部分。

為什麼要使用模組

  1. 增加可維護性:由於每個模組都是獨立的,每個人寫的程式碼是不會相互影響的,在維護程式碼的時候很好排查是哪個模組出錯。
  2. 可複用性:在日常的開發中,特別是大點的專案,程式碼的可複用性就更重要了,也許你會用複製貼上的形式,但是直接一個 import 命令就可以搞定,豈不快哉。
  3. 避免命名汙染:在 javascript 指令碼中,所有的 js 檔案的頂級作用域建立的變數,會被新增到共享的全域性作用域,這就會導致不同的人開發的程式碼可能會有相同的變數名,導致變數名汙染。

如何使用

匯出模組

匯出模組所用的命令是 export。

前面也提到一個模組就是一個 javascript 檔案,在這個模組中定義的變數,外部是無法獲取到的,只有通過 export 匯出的變數其他模組才可以用

最簡單的匯出方式就是在宣告的變數、函式、類前面加一個 export

// export1.js 

// 匯出變數
export let name = `桃翁`;

// 匯出函式
export function print() {
    console.log("歡迎關注公眾號:前端桃園");
}

// 匯出類
export class Person {
    constructor(name) {
        this.name = name;
    }
}
// 私有函式
function privateFunction () {
    console.log(`我是私有函式,外部訪問不了我`);
}

注意

1. 被匯出的函式或者類,都必須要有名稱,意思就是說不能用這種方式匯出匿名函式或者匿名類。
2. privateFunction 函式,沒有加 export 命令,被當做這個模組的私有變數,其他模組是訪問不到的。

除了上面那種匯出方式,還有另外一種

// export2.js

// 匯出變數
let name = `桃翁`;

// 匯出函式
function print() {
    return `歡迎關注公眾號:前端桃園`;
}

// 匯出類
class Person {
    constructor(name) {
        this.name = name;
    }
}

// 私有函式
function privateFunction () {
    return `我是私有函式,外部訪問不了我`;
}

export { name, print, Person }

上面這種寫法匯入一組變數,與 export1.js 是等價的。

匯入模組

匯入的模組可以理解為是生產者(或者服務的提供者),而使用匯入的模組的模組就是消費者。

匯入模組的命令是 import, import 的基本形式如下:

import { var1, var2 } from `./example.js`

import 語句包含兩部分:一是匯入需要的識別符號,二是模組的來源。

注意:瀏覽器中模組來源要以「/」或者 「./」 或者 「../」開頭 或者 url 形式,不然會報錯。

例如我們匯入 export1.js 模組,可以這麼匯入

// import1.js
import { name, print, Person } from `./export1.js`;

console.log(name); // 桃翁

console.log(print()); // 歡迎關注公眾號:前端桃園

// 報錯, 不能定義相同名字變數
let name = 2333; 

// 報錯,不能重新賦值
name = "小豬";

可以看到匯入繫結(這裡不理解繫結,文章後面會解釋)時,形式類似於物件解構,但實際上並無關聯。

當匯入繫結的時候,繫結類似於使用了 const 定義,意味著不能定義相同的變數名,但是沒有暫時性死區特性(但是在 深入理解ES6 這本書裡面說是有暫時性死區限制,我在 chrome 上測試了的,讀者希望也去試下,到底受不受限制)。

let name = 2333;

上面這行程式碼會報錯。

名稱空間匯入

這種匯入方式是把整個生產者模組當做單一物件匯入,所有的匯出被當做物件的屬性。

// import2.js
import * as namespace from `./export1.js`

console.log(namespace.name); // 桃翁

console.log(namespace.print()); // 歡迎關注公眾號:前端桃園

重新命名匯入匯出

有時候你並不想匯出變數的原名稱,需要重新命名,這個時候只需要使用 as 關鍵字來制定新的名字即可。

重新命名匯出

// export3.js

function print() {
    return `歡迎關注公眾號:前端桃園`;
}

export { print as advertising }

導重新命名入

拿上面匯出的舉例子

// import3.js
import { advertising as print } from `./export3.js`

console.log(typeof advertising); // "undefined"

console.log(print()); // 歡迎關注公眾號:前端桃園 

此程式碼匯入 advertising 函式並重新命名為了 print ,這意味著此模組中 advertising 識別符號不存在了。

default 關鍵字

default 關鍵字是用來做預設匯入匯出的。

預設匯出

// defaultExport.js

// 第一種預設匯出方式
export default function print() {
    return `歡迎關注公眾號:前端桃園`;
}

// 第二種預設匯出方式
function print() {
    return `歡迎關注公眾號:前端桃園`;
}

export default print;

// 第三種預設匯出方式
function print() {
    return `歡迎關注公眾號:前端桃園`;
}

export { print as default }

default 這個關鍵字在 JS 中具有特殊含義,既可以作為同命名匯出,又標明瞭模組需要使用預設值。

注意: 一個模組中只能有一個預設匯出。

預設匯入

預設匯入和一般的匯入不同之處就是不需要寫大括號了,看起來更簡潔。

把上面 defaultExport.js 模組匯出的作為例子

import print from `./defaultExport.js`

console.log(print()); // 歡迎關注公眾號:前端桃園 

那如果既有預設的又有非預設的怎麼匯入呢?看例子就明白了

// defaultImport1.js

let name = `桃翁`;

function print() {
    return `歡迎關注公眾號:前端桃園`;
}

export { name, print as default }
// defaultImport2.js

import print, { name } from `./defaultImport1.js`

console.log(print()); // 歡迎關注公眾號:前端桃園

console.log(name); // 桃翁

混合匯入需要把預設匯入的名稱放在最前面,然後用逗號和後面非預設匯出的分割開。

思考了很久是否應該加上進階內容,本來是想寫入門級系列的,但是想了想,還是都寫進來吧,入門的看入門前面基礎,深入理解的看進階。

進階

進階部分主要介紹 模組的幾個特性

  • 靜態執行
  • 動態關聯
  • 模組不會重複執行

靜態執行

所謂靜態執行其實就是在編譯階段就需要確定模組的依賴關係,那麼就會出現 import 命令會優先於模組其他內容的執行,會提前到編譯階段執行。

// static1.js
console.log(`佩奇`);

import { nouse } from `./static2.js`

// static2.js
export function nouse() {
    return `我是不需要的`;
}

console.log(`小豬`);

可以看到最後輸出的應該是「小豬」先輸出,而「佩奇」後輸出,可以得出雖然 static2.js 在後面引入,但是會被提升到模組的最前面先執行。

這也是我前面所說的不受暫時性死區原因之一,在這裡可以寫一個例子試試:

// static3.js
console.log(nouse());

import { nouse } from `./static2.js`

// 結果:
// 小豬
// 我是不需要的

經檢驗確實是可以在 import 之前使用匯入的繫結。

靜態執行還會導致一個問題,那就是不能動態匯入模組。

// 報錯
if (flag) {
    import { nouse } from `./static3.js`
}

// 報錯
import { `no` + `use` } from `./static3.js`

因為 import 是靜態執行的,所以在靜態(詞法)分析階段,是沒法得到表示式或者變數的值的。

但是為了解決這個問題,因為了 import() 這個函式,這個算擴充套件內容吧,寫太多了我怕沒人看完了,後面會有擴充套件閱讀連結。

動態關聯

所謂的動態關聯,其實就是一種繫結關係, 這是 ES6 非常重要的特性,一定仔細閱讀。

在 ES6 的模組中,輸出的不是物件的拷貝,不管是引用型別還是基本型別, 都是動態關聯模組中的值,。

// dynamic1.js
export let name = `桃翁`;

export function setName(name) {
    name = name;
}

// dynamic2.js
import { name, setName } from `./dynamic1.js`

console.log(name); // 桃翁

setName(`不要臉`);

console.log(name); // 不要臉

奇蹟般的發現在 dynamic2.js 模組中可以修改 dynamic1.js 模組裡面的值, 並且反應到 name 繫結上(這個是重點,這個反應到了消費者模組), 所以我們把匯入的變數叫做繫結。

在生產者模組匯出的變數與消費者模組匯入的變數會有一個繫結關係,無論前者或者後者發生改變,都會互相影響。

注意區分在一個檔案或模組中基本型別的賦值,兩者是互不影響的。

模組不會重複執行

這個特性比較好理解,就是如果從一個生產者模組中分別匯入繫結,而不是一次性匯入,生產者模組不會執行多次。

// noRepeat1.js
export let name = `桃翁`;

export let age = `22`;

console.log(`我正在執行。。。`);

// noRepeat2.js
import { name } from `./noRepeat1.js`;
import { age } from `./noRepeat1.js`;

console.log(name);
console.log(age);

// 結果
// 我正在執行。。。
// 桃翁
// 22

雖然匯入了兩次,但是 noRepeat1.js 只有執行一次。若同一個應用(注意是同一個應用不是模組)中匯入同一個模組,則那些模組都會使用一個模組例項,意思就是說是一個單例。

後記

碼字不易,寫技術文章是真的累,作者花的時間至少是讀者讀的時間的十倍。在此想到阮老師寫了那麼多文章,不知道是花了多少時間,竟然還有人這麼恨他,攻擊他的網站。

我在文章中給我公眾號打了很多廣告,在此抱個歉,剛運營的公眾號,需要拉點粉絲,不喜歡的注重內容就好。

擴充

原生ECMAScript模組: 動態 import()

相關文章