詳解 JavaScript 中的模塊、Import和Export

做者:Tania Rascia

翻譯:瘋狂的技術宅javascript

原文:https://www.taniarascia.com/j...html

在互聯網的洪荒時代,網站主要用 HTML和 CSS 開發的。若是將 JavaScript 加載到頁面中,一般是以小片斷的形式提供效果和交互,通常會把全部的 JavaScript 代碼全都寫在一個文件中,並加載到一個 script 標籤中。儘管能夠把 JavaScript 拆分爲多個文件,可是全部的變量和函數仍然會被添加到全局做用域中。前端

可是後來 JavaScript 在瀏覽器中發揮着重要的做用,迫切須要使用第三方代碼來完成常見任務,而且須要把代碼分解爲模塊化的文件,避免污染全局命名空間。java

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

模塊化編程

在 JavaScript 中出現模塊的概念以前,當咱們想要把本身的代碼組織爲多個塊時,通常會建立多個文件,而且將它們連接爲單獨的腳本。下面先舉例說明,首先建立一個 index.html 文件和兩個JavaScript文件「 functions.jsscript.js面試

index.html 文件用來顯示兩個數字的和、差、乘積和商,並連接到 script 標籤中的兩個 JavaScript 文件。打開 index.html 並添加如下代碼:chrome

index.htmlnpm

<!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>Division</h3>
    <p id="division"></p>

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

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

functions.js 文件中包含將會在第二個腳本中用到的數學函數。打開文件並添加如下內容:segmentfault

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('division').textContent = quotient(x, y)

保存以後在瀏覽器中打開 index.html 能夠看到全部結果:

image.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('division').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 來實現。

173382ede7319973.gif


本文首發微信公衆號:前端先鋒

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章

歡迎掃描二維碼關注公衆號,天天都給你推送新鮮的前端技術文章


歡迎繼續閱讀本專欄其它高贊文章:


相關文章
相關標籤/搜索