發(fā)布于:2020-12-19 18:52:28
0
121
0
在Web的早期,網(wǎng)站主要由HTML和CSS組成。如果將任何JavaScript加載到頁面中,則通常是以小片段的形式提供效果和交互性。結(jié)果,JavaScript程序通常被完全寫在一個文件中,并加載到script
標(biāo)簽中。開發(fā)人員可以將JavaScript分解為多個文件,但是所有變量和函數(shù)仍將添加到全局范圍內(nèi)。
但是隨著網(wǎng)站隨著Angular,React和Vue等框架的出現(xiàn)而發(fā)展,并且隨著公司創(chuàng)建高級Web應(yīng)用程序而不是桌面應(yīng)用程序,JavaScript現(xiàn)在在瀏覽器中扮演著重要角色。結(jié)果,迫切需要使用第三方代碼來執(zhí)行常見任務(wù),將代碼分解為模塊化文件并避免污染全局名稱空間。
在ECMAScript的2015年規(guī)范引入模塊JavaScript語言,它允許使用import
和export
聲明。在本教程中,您將學(xué)習(xí)什么是JavaScript模塊以及如何使用import
和export
組織代碼。
在JavaScript中出現(xiàn)模塊概念之前,當(dāng)開發(fā)人員希望將其代碼組織為段時,他們將創(chuàng)建多個文件并將其鏈接為單獨(dú)的腳本。為了演示這一點(diǎn),請創(chuàng)建一個示例index.html
文件和兩個JavaScript文件,functions.js
以及script.js
。
該index.html
文件將顯示兩個數(shù)字的和,差,乘積和商,并鏈接到script
標(biāo)記中的兩個JavaScript文件。index.html
在文本編輯器中打開并添加以下代碼:
<div class="filename" "="" style="box-sizing: border-box; font-size: 0.9rem; margin-bottom: -0.8rem; padding: 0.3rem 1rem 0.5rem; line-height: 1; font-family: "Myriad Pro", sans-serif; background: linear-gradient(rgb(234, 234, 234), rgb(210, 210, 210)) rgb(255, 255, 255); border: 1px solid rgb(185, 188, 189); color: rgb(77, 73, 77); z-index: 2; margin-left: auto; margin-right: auto; text-shadow: rgba(255, 255, 255, 0.5) 0px 1px 0px; box-shadow: rgba(255, 255, 255, 0.5) 0px 1px 0px inset, rgb(81, 81, 81) 0px 1px 0px; text-align: center; border-top-left-radius: 0.3rem; border-top-right-radius: 0.3rem; white-space: normal;">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>Division</h3>
<p id="division"></p>
<script src="functions.js"></script>
<script src="script.js"></script>
</body></html>
該HTML將在標(biāo)頭中顯示變量的值,x
并y
在h2
以下p
元素中顯示對這些變量的操作的值。id
元素的屬性是為DOM操作設(shè)置的,它將在script.js
文件中發(fā)生;該文件也將設(shè)定值x
和y
。有關(guān)HTML的更多信息,請查看我們的“如何使用HTML建立網(wǎng)站”系列。
該functions.js
文件將包含將在第二個腳本中使用的數(shù)學(xué)函數(shù)。打開functions.js
文件并添加以下內(nèi)容:
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
函數(shù)應(yīng)用于它們,并顯示結(jié)果:
script.js
const x = 10const y = 5document.getElementById('x').textContent = xdocument.getElementById('y').textContent = ydocument.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)
設(shè)置并保存這些文件。
對于帶有一些小腳本的網(wǎng)站,這是一種劃分代碼的有效方法。但是,此方法存在一些問題,包括:
污染全局名稱空間:對象中已經(jīng)存在您在腳本中創(chuàng)建的所有變量sum
(difference
,等)window
。如果您嘗試使用sum
在另一個文件中調(diào)用的另一個變量,將很難知道在腳本中的任何時候都將使用哪個值,因為它們都將使用相同的window.sum
變量。變量可以私有的唯一方法是將其置于函數(shù)范圍內(nèi)。甚至id
在DOM中的x
和之間可能存在沖突var x
。
依賴性管理:必須從上到下依次加載腳本,以確保可以使用正確的變量。將腳本另存為不同的文件會產(chǎn)生分離的錯覺,但本質(zhì)上與<script>
在瀏覽器頁面中具有單個內(nèi)聯(lián)相同。
在ES6將本機(jī)模塊添加到JavaScript語言之前,社區(qū)嘗試提供幾種解決方案。第一個解決方案是使用普通JavaScript編寫的,例如將所有代碼編寫到對象或立即調(diào)用的函數(shù)表達(dá)式(IIFE)中,并將它們放在全局命名空間中的單個對象上。這是對多腳本方法的一種改進(jìn),但是仍然存在將至少一個對象放入全局名稱空間的相同問題,并且沒有使在第三方之間一致地共享代碼的問題變得更加容易。
之后,出現(xiàn)了一些模塊解決方案:CommonJS(一種在Node.js中實現(xiàn)的同步方法),異步模塊定義(AMD)(一種異步方法)和通用模塊定義(UMD)(一種通用的方法)支持以前兩種樣式的方法。
這些解決方案的出現(xiàn)使開發(fā)人員更易于以軟件包,可分發(fā)和共享的模塊(例如在npm上找到的模塊)的形式共享和重用代碼。但是,由于存在許多解決方案,并且都不是JavaScript固有的,因此必須實現(xiàn)Babel,Webpack或Browserify之類的工具才能在瀏覽器中使用模塊。
由于多文件方法存在許多問題,并且所提出的解決方案很復(fù)雜,因此開發(fā)人員對將模塊化編程方法引入JavaScript語言很感興趣。因此,ECMAScript 2015支持使用JavaScript模塊。
甲模塊是代碼束充當(dāng)一個接口到其它模塊的用途,以及能夠依靠其它模塊的功能性的功能。模塊導(dǎo)出以提供代碼,而導(dǎo)入以使用其他代碼。模塊之所以有用,是因為它們允許開發(fā)人員重用代碼,它們提供許多開發(fā)人員可以使用的穩(wěn)定,一致的接口,并且它們不會污染全局名稱空間。
模塊(有時稱為ECMAScript模塊或ES模塊)現(xiàn)在可以在JavaScript中本地使用,在本教程的其余部分中,您將探索如何在代碼中使用和實現(xiàn)它們。
JavaScript中的模塊使用import
和export
關(guān)鍵字:
import
:用于讀取從另一個模塊導(dǎo)出的代碼。
export
:用于向其他模塊提供代碼。
為了演示如何使用此功能,請將functions.js
文件更新為模塊并導(dǎo)出功能。您將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}
現(xiàn)在,在中script.js
,您將使用import
來從functions.js
文件頂部的模塊中檢索代碼。
注意:
import
必須始終位于文件的頂部,然后再包含其他路徑(./
在這種情況下)。
將以下突出顯示的代碼添加到script.js
:
script.js
import { sum, difference, product, quotient } from './functions.js'const x = 10const y = 5document.getElementById('x').textContent = xdocument.getElementById('y').textContent = ydocument.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)
請注意,通過在花括號中命名單個函數(shù)來導(dǎo)入它們。
為了確保將此代碼作為模塊而不是常規(guī)腳本加載,請?zhí)砑?/span>type="module"
到中的script
標(biāo)記index.html
。使用import
或export
必須使用此屬性的任何代碼:
index.html
<script
type="module" src="functions.js"></script><script
type="module" src="script.js"></script>
此時,您將能夠使用更新來重新加載頁面,并且網(wǎng)站現(xiàn)在將使用模塊。瀏覽器支持很高,但是可以使用caniuse來檢查哪些瀏覽器支持它。請注意,如果您將文件視為直接鏈接到本地文件,則將遇到此錯誤:
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.
由于采用了CORS策略,因此必須在服務(wù)器環(huán)境中使用模塊,您可以在本地通過http-server或通過托管服務(wù)提供商在Internet上進(jìn)行設(shè)置。
模塊在某些方面與常規(guī)腳本不同:
模塊不會向global(window
)范圍添加任何內(nèi)容。
模塊始終處于嚴(yán)格模式。
將同一模塊兩次加載到同一文件中將無效,因為模塊僅執(zhí)行一次/
模塊需要服務(wù)器環(huán)境。
模塊仍然經(jīng)常與捆綁程序(如Webpack)一起使用,以增加對瀏覽器的支持和附加功能,但它們也可以直接在瀏覽器中使用。
接下來,您將探索使用import
andexport
語法的更多方法。
如前所述,使用export
語法將允許您分別導(dǎo)入按其名稱導(dǎo)出的值。以以下簡化版本為例functions.js
:
functions.js
export function sum() {}export function difference() {}
這將允許您使用花括號導(dǎo)入sum
并按difference
名稱命名:
script.js
import {sum, difference} from './functions.js'
也可以使用別名來重命名該函數(shù)。您可以這樣做以避免在同一模塊中命名沖突。在此示例中,sum
將重命名為add
,difference
并將重命名為subtract
。
script.js
import {
sum as add,
difference as subtract} from './functions.js'add(1, 2) // 3
在add()
這里調(diào)用將產(chǎn)生sum()
函數(shù)的結(jié)果。
使用*
語法,可以將整個模塊的內(nèi)容導(dǎo)入到一個對象中。在這種情況下,sum
并且difference
將成為對方法的mathFunctions
對象。
script.js
import * as mathFunctions from './functions.js'mathFunctions.sum(1, 2) // 3mathFunctions.difference(10, 3) // 7
基本值,函數(shù)表達(dá)式和定義,異步函數(shù),類和實例化的類都可以導(dǎo)出,只要它們具有標(biāo)識符即可:
// Primitive valuesexport const number = 100export const string = 'string'export const undef = undefinedexport const empty = nullexport const obj = {name: 'Homer'}export const array = ['Bart', 'Lisa', 'Maggie']// Function expressionexport const sum = (x, y) => x + y// Function defintionexport function difference(x, y) {
return x - y}// Asynchronous functionexport async function getBooks() {}// Classexport class Book {
constructor(name, author) {
this.name = name this.author = author }}// Instantiated classexport const book = new Book('Lord of the Rings', 'J. R. R. Tolkein')
所有這些出口都可以成功進(jìn)口。下一節(jié)將探討的另一種導(dǎo)出類型稱為默認(rèn)導(dǎo)出。
在前面的示例中,您導(dǎo)出了多個命名的導(dǎo)出,并將它們分別導(dǎo)入或作為一個對象導(dǎo)入,每個導(dǎo)出都作為對象上的方法。模塊也可以使用default
關(guān)鍵字包含默認(rèn)導(dǎo)出。默認(rèn)導(dǎo)出不會使用大括號導(dǎo)入,而是直接導(dǎo)入到命名標(biāo)識符中。
以functions.js
文件的以下內(nèi)容為例:
functions.js
export default function sum(x, y) {
return x + y}
在script.js
文件中,您可以導(dǎo)入默認(rèn)函數(shù),sum
如下所示:
script.js
import sum from './functions.js'sum(1, 2) // 3
這很危險,因為在導(dǎo)入過程中對默認(rèn)導(dǎo)出的命名沒有任何限制。在此示例中,默認(rèn)功能被導(dǎo)入,就像difference
它實際上是該sum
功能一樣:
script.js
import difference from './functions.js'difference(1, 2) // 3
因此,通常首選使用命名出口。與命名導(dǎo)出不同,默認(rèn)導(dǎo)出不需要標(biāo)識符-原始值本身或匿名函數(shù)都可以用作默認(rèn)導(dǎo)出。以下是用作默認(rèn)導(dǎo)出的對象的示例:
functions.js
export default {
name: 'Lord of the Rings',
author: 'J. R. R. Tolkein',}
您可以book
通過以下方式導(dǎo)入它:
functions.js
import book from './functions.js'
同樣,以下示例演示了如何將匿名箭頭功能導(dǎo)出為默認(rèn)導(dǎo)出:
functions.js
export default () => 'This function is anonymous'
這可以通過以下方式導(dǎo)入script.js
:
script.js
import anonymousFunction from './functions.js'
命名導(dǎo)出和默認(rèn)導(dǎo)出可以彼此并用,如在此模塊中導(dǎo)出兩個命名值和一個默認(rèn)值一樣:
functions.js
export const length = 10export const width = 5export default function perimeter(x, y) {
return 2 * (x + y)}
您可以使用以下命令導(dǎo)入這些變量和默認(rèn)函數(shù):
script.js
import calculatePerimeter, {length, width} from './functions.js'calculatePerimeter(length, width) // 30
現(xiàn)在,默認(rèn)值和命名值都可用于腳本。
模塊化編程設(shè)計實踐使您可以將代碼分成單獨(dú)的組件,這有助于使代碼可重復(fù)使用和保持一致,同時還可以保護(hù)全局名稱空間??梢允褂?/span>import
和export
關(guān)鍵字在本機(jī)JavaScript中實現(xiàn)模塊接口。在本文中,您了解了JavaSvript中模塊的歷史記錄,如何將JavaScript文件分離為多個頂級腳本,如何使用模塊化方法更新這些文件,以及命名和默認(rèn)導(dǎo)出的import
andexport
語法。
要了解有關(guān)JavaScript中的模塊的更多信息,請閱讀Mozilla開發(fā)人員網(wǎng)絡(luò)上的模塊。如果您想探索Node.js中的模塊,請嘗試我們的如何創(chuàng)建Node.js模塊教程。