一起看看 JavaScript 中的模組、Import和Export

2020-12-29 21:00:49
欄目介紹模組、Import和Export的用法

推薦(免費):(視訊)

在網際網路的洪荒時代,網站主要用 HTML和 CSS 開發的。如果將 JavaScript 載入到頁面中,通常是以小片段的形式提供效果和互動,一般會把所有的 JavaScript 程式碼全都寫在一個檔案中,並載入到一個 script 標籤中。儘管可以把 JavaScript 拆分為多個檔案,但是所有的變數和函數仍然會被新增到全域性作用域中。

但是後來 JavaScript 在瀏覽器中發揮著重要的作用,迫切需要使用第三方程式碼來完成常見任務,並且需要把程式碼分解為模組化的檔案,避免汙染全域性名稱空間。

ECMAScript 2015 規範在 JavaScript 語言中引入了 module,也有了 import 和 export 語句。在本文中,我們一起來學習 JavaScript 模組,以及怎樣用 importexport 來組織程式碼。

模組化程式設計

在 JavaScript 中出現模組的概念之前,當我們想要把自己的程式碼組織為多個塊時,一般會建立多個檔案,並且將它們連結為單獨的指令碼。下面先舉例說明,首先建立一個 index.html 檔案和兩個JavaScript檔案「 functions.jsscript.js

index.html 檔案用來顯示兩個數位的和、差、乘積和商,並連結到 script 標籤中的兩個 JavaScript 檔案。開啟 index.html 並新增以下程式碼:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>JavaScript Modules</title>
  </head>

  <body>
    <h1>Answers</h1>
    <h2><strong id="x"></strong> and <strong id="y"></strong></h2>

    <h3>Addition</h3>
    <p id="addition"></p>

    <h3>Subtraction</h3>
    <p id="subtraction"></p>

    <h3>Multiplication</h3>
    <p id="multiplication"></p>

    <h3>pision</h3>
    <p id="pision"></p>

    <script src="functions.js"></script>
    <script src="script.js"></script>
  </body>
</html>

這個頁面很簡單,就不詳細說明了。

functions.js 檔案中包含將會在第二個指令碼中用到的數學函數。開啟檔案並新增以下內容:

functions.js

function sum(x, y) {
  return x + y
}

function difference(x, y) {
  return x - y
}

function product(x, y) {
  return x * y
}

function quotient(x, y) {
  return x / y
}

最後,script.js 檔案用來確定 x 和 y 的值,以及呼叫前面那些函數並顯示結果:

script.js

const x = 10
const y = 5

document.getElementById('x').textContent = x
document.getElementById('y').textContent = y

document.getElementById('addition').textContent = sum(x, y)
document.getElementById('subtraction').textContent = difference(x, y)
document.getElementById('multiplication').textContent = product(x, y)
document.getElementById('pision').textContent = quotient(x, y)

儲存之後在瀏覽器中開啟 index.html 可以看到所有結果:

67f3c37d5a143b6c6c97042f69a886a.png

對於只需要一些小指令碼的網站,這不失為一種有效的組織程式碼的方法。但是這種方法存在一些問題:

  • 汙染全域性名稱空間:你在指令碼中建立的所有變數(sumdifference 等)現在都存在於 window 物件中。如果你打算在另一個檔案中使用另一個名為 sum 的變數,會很難知道在指令碼的其它位置到底用的是哪一個值變數,因為它們用的都是相同的 window.sum 變數。唯一可以使變數私有的方法是將其放在函數的作用域中。甚至在 DOM 中名為 xid 可能會和 var x 存在衝突。
  • 依賴管理:必須從上到下依次載入指令碼來確保可以使用正確的變數。將指令碼分別儲存存為不同檔案會產生分離的錯覺,但本質上與放在頁面中的單個 <script> 中相同。

在 ES6 把原生模組新增到 JavaScript 語言之前,社群曾經嘗試著提供了幾種解決方案。第一個解決方案是用原生 JavaScript 編寫的,例如將所有程式碼都寫在 objects 或立即呼叫的函數表示式(IIFE)中,並將它們放在全域性名稱空間中的單個物件上。這是對多指令碼方法的一種改進,但是仍然存在將至少一個物件放入全域性名稱空間的問題,並沒有使在第三方之間一致地共用程式碼的問題變得更加容易。

之後又出現了一些模組解決方案:CommonJS 是一種在 Node.js 實現的同步方法,非同步模組定義(AMD)是一種非同步方法,還有支援前面兩種樣式的通用方法——通用模組定義(UMD)。

這些解決方案的出現使我們可以更輕鬆地以的形式共用和重用程式碼,也就是可以分發和共用的模組,例如 npm。但是由於存在許多解決方案,並且都不是 JavaScript 原生的,所以需要依靠 Babel、Webpack 或 Browserify之類的工具才能在瀏覽器中使用。

由於多檔案方法存在許多問題,並且解決方案很複雜,所以開發人員對把模組化開發的方法引入 JavaScript 語言非常感興趣。於是 ECMAScript 2015 開始支援 JavaScript module

module 是一組程式碼,用來提供其他模組所使用的功能,並能使用其他模組的功能。 export 模組提供程式碼,import 模組使用其他程式碼。模組之所以有用,是因為它們允許我們重用程式碼,它們提供了許多可用的穩定、一致的介面,並且不會汙染全域性名稱空間。

模組(有時稱為 ES 模組)現在可以在原生 JavaScript 中使用,在本文中,我們一起來探索怎樣在程式碼中使用及實現。

原生 JavaScript 模組

JavaScript 中的模組使用importexport 關鍵字:

  • import:用於讀取從另一個模組匯出的程式碼。
  • export:用於向其他模組提供程式碼。

接下來把前面的的 functions.js 檔案更新為模組並匯出函數。在每個函數的前面新增 export

functions.js

export function sum(x, y) {
  return x + y
}

export function difference(x, y) {
  return x - y
}

export function product(x, y) {
  return x * y
}

export function quotient(x, y) {
  return x / y
}

script.js 中用 import 從前面的 functions.js 模組中檢索程式碼。

注意import 必須始終位於檔案的頂部,然後再寫其他程式碼,並且還必須包括相對路徑(在這個例子裡為 ./)。

script.js 中的程式碼改成下面的樣子:

script.js

import { sum, difference, product, quotient } from './functions.js'

const x = 10
const y = 5

document.getElementById('x').textContent = x
document.getElementById('y').textContent = y

document.getElementById('addition').textContent = sum(x, y)
document.getElementById('subtraction').textContent = difference(x, y)
document.getElementById('multiplication').textContent = product(x, y)
document.getElementById('pision').textContent = quotient(x, y)

注意:要通過在花括號中命名單個函數來匯入。

為了確保程式碼作為模組匯入,而不是作為常規指令碼載入,要在 index.html 中的 script 標籤中新增type="module"。任何使用 importexport 的程式碼都必須使用這個屬性:

index.html

<script 
  type="module" src="functions.js">
</script>
<script 
  type="module" src="script.js">
</script>

由於受限於 CORS 策略,必須在伺服器環境中使用模組,否則會出現下面的錯誤:

Access to script at 'file:///Users/your_file_path/script.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: http, data, chrome, chrome-extension, chrome-untrusted, https.

模組與常規指令碼不一樣的地方:

  • 模組不會向全域性(window)作用域新增任何內容。
  • 模組始終處於嚴格模式。
  • 在同一檔案中把同一模組載入兩次不會出問題,因為模組僅執行一次
  • 模組需要伺服器環境。

模組仍然經常與打包程式(如 Webpack)一起配合使用,用來增加對瀏覽器的支援和附加功能,但它們也可以直接用在瀏覽器中。

接下來探索更多使用 importexport 語法的方式。

命名匯出

如前所述,使用 export 語法允許你分別匯入按名稱匯出的值。以這個 function.js 的簡化版本為例:

functions.js

export function sum() {}
export function difference() {}

這樣允許你用花括號按名稱匯入 sumdifference

script.js

import {sum, difference} from './functions.js'

也可以用別名來重新命名該函數。這樣可以避免在同一模組中產生命名衝突。在這個例子中,sum 將重新命名為 add,而 difference 將重新命名為 subtract

script.js

import {
  sum as add,
  difference as subtract
} from './functions.js'

add(1, 2) // 3

在這裡呼叫 add() 將產生 sum() 函數的結果。

使用 * 語法可以將整個模組的內容匯入到一個物件中。在這種情況下,sumdifference 將成為 mathFunctions 物件上的方法。

script.js

import * as mathFunctions from './functions.js'

mathFunctions.sum(1, 2) // 3
mathFunctions.difference(10, 3) // 7

原始值、函數表示式和定義、非同步函數、類和範例化的類都可以匯出,只要它們有識別符號就行:

// 原始值
export const number = 100
export const string = 'string'
export const undef = undefined
export const empty = null
export const obj = {name: 'Homer'}
export const array = ['Bart', 'Lisa', 'Maggie']

// 函數表示式
export const sum = (x, y) => x + y

// 函數定義
export function difference(x, y) {
  return x - y
}

// 匿名函數
export async function getBooks() {}

// 類
export class Book {
  constructor(name, author) {
    this.name = name
    this.author = author
  }
}

// 範例化類
export const book = new Book('Lord of the Rings', 'J. R. R. Tolkein')

所有這些匯出都可以成功被匯入。接下來要探討的另一種匯出型別稱為預設匯出。

預設匯出

在前面的例子中我們匯出了多個命名的匯出,並分別或作為一個物件匯入了每個匯出,將每個匯出作為物件上的方法。模組也可以用關鍵字 default 包含預設匯出。預設匯出不使用大括號匯入,而是直接匯入到命名識別符號中。

functions.js 檔案為例:

functions.js

export default function sum(x, y) {
  return x + y
}

script.js 檔案中,可以用以下命令將預設函數匯入為 sum

script.js

import sum from './functions.js'

sum(1, 2) // 3

不過這樣做很危險,因為在匯入過程中對預設匯出的命名沒有做任何限制。在這個例子中,預設函數被匯入為 difference,儘管它實際上是 sum 函數:

script.js

import difference from './functions.js'

difference(1, 2) // 3

所以一般首選使用命名匯出。與命名匯出不同,預設匯出不需要識別符號——原始值本身或匿名函數都可以用作預設匯出。以下是用作預設匯出的物件的範例:

functions.js

export default {
  name: 'Lord of the Rings',
  author: 'J. R. R. Tolkein',
}

可以用以下命令將其作為 book 匯入:

functions.js

import book from './functions.js'

同樣,下面的例子演示瞭如何將匿名箭頭函數匯出為預設匯出:

functions.js

export default () => 'This function is anonymous'

可以這樣匯入:

script.js

import anonymousFunction from './functions.js'

命名匯出和預設匯出可以彼此並用,例如在這個模組中,匯出兩個命名值和一個預設值:

functions.js

export const length = 10
export const width = 5

export default function perimeter(x, y) {
  return 2 * (x + y)
}

可以用以下命令匯入這些變數和預設函數:

script.js

import calculatePerimeter, {length, width} from './functions.js'

calculatePerimeter(length, width) // 30

現在預設值和命名值都可用於指令碼了。

總結

模組化程式設計設計允許我們把程式碼分成單個元件,這有助於程式碼重用,同時還可以保護全域性名稱空間。一個模組介面可以在原生 JavaScript 中用關鍵字 importexport 來實現。

以上就是一起看看 JavaScript 中的模組、Import和Export的詳細內容,更多請關注TW511.COM其它相關文章!