JavaScriptでのモジュールとインポートおよびエクスポートステートメントの理解
著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。
序章
Webの初期の頃、Webサイトは主にHTMLとCSSで構成されていました。 JavaScriptがページに読み込まれた場合、それは通常、効果と対話性を提供する小さなスニペットの形式でした。 その結果、JavaScriptプログラムは、多くの場合、完全に1つのファイルに書き込まれ、script
タグにロードされました。 開発者はJavaScriptを複数のファイルに分割できますが、すべての変数と関数は引き続きグローバルスコープに追加されます。
しかし、 Angular 、 React 、 Vue などのフレームワークの出現により、Webサイトが進化し、デスクトップアプリケーションの代わりに高度なWebアプリケーションを作成する企業とともに、JavaScriptが登場しました。ブラウザで主要な役割を果たします。 その結果、一般的なタスクにサードパーティのコードを使用し、コードをモジュラーファイルに分割し、グローバル名前空間の汚染を回避する必要性がはるかに高くなります。
ECMAScript 2015 仕様では、JavaScript言語に modules が導入され、import
およびexport
ステートメントの使用が可能になりました。 このチュートリアルでは、JavaScriptモジュールとは何か、およびimport
とexport
を使用してコードを整理する方法を学習します。
モジュラープログラミング
モジュールの概念がJavaScriptに登場する前は、開発者がコードをセグメントに編成したい場合、複数のファイルを作成し、それらに個別のスクリプトとしてリンクしていました。 これを示すために、例のindex.html
ファイルと2つのJavaScriptファイル、functions.js
とscript.js
を作成します。
index.html
ファイルは、2つの数値の合計、差、積、および商を表示し、script
タグ内の2つのJavaScriptファイルにリンクします。 テキストエディタで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は、変数x
とy
の値をh2
ヘッダーに表示し、これらの変数に対する操作の値を次のp
要素に表示します。 要素のid
属性は、DOM操作に設定されます。これはscript.js
ファイルで発生します。 このファイルは、x
およびy
の値も設定します。 HTMLの詳細については、HTMLシリーズを使用してWebサイトを構築する方法を確認してください。
functions.js
ファイルには、2番目のスクリプトで使用される数学関数が含まれます。 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
の値を決定し、それらに関数を適用して、結果を表示します。
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を開くと、すべての結果を含むWebサイトを表示できます。
いくつかの小さなスクリプトがあるWebサイトの場合、これはコードを分割する効果的な方法です。 ただし、このアプローチに関連するいくつかの問題があります。
- グローバル名前空間の汚染:スクリプトで作成したすべての変数(
sum
、difference
など)がウィンドウオブジェクトに存在するようになりました。 別のファイルでsum
という別の変数を使用しようとすると、すべて同じwindow.sum
を使用するため、スクリプトのどの時点でもどの値が使用されるかを知るのが難しくなります。変数。 変数をプライベートにする唯一の方法は、変数を関数スコープ内に置くことでした。x
という名前のDOMのid
とvar x
の間で競合が発生する可能性もあります。 - 依存関係の管理:正しい変数が使用可能であることを確認するために、スクリプトを上から下に順番にロードする必要があります。 スクリプトを別のファイルとして保存すると、分離のように見えますが、基本的には、ブラウザページに単一のインライン
<script>
があるのと同じです。
ES6がJavaScript言語にネイティブモジュールを追加する前に、コミュニティはいくつかの解決策を考え出そうとしました。 最初のソリューションは、すべてのコードをオブジェクトまたは即時呼び出し関数式(IIFE)に記述し、それらをグローバル名前空間の単一オブジェクトに配置するなど、バニラJavaScriptで記述されました。 これはマルチスクリプトアプローチの改善でしたが、グローバル名前空間に少なくとも1つのオブジェクトを配置するという同じ問題があり、サードパーティ間でコードを一貫して共有するという問題は簡単にはなりませんでした。
その後、いくつかのモジュールソリューションが登場しました。 CommonJS 、 Node.js 、 Asynchronous Module Definition(AMD)に実装された同期アプローチ。非同期アプローチ、および Universal Module Definition(UMD)。これは、以前の両方のスタイルをサポートするユニバーサルアプローチであることが意図されていました。
これらのソリューションの出現により、開発者はパッケージ、 npm にあるような、配布および共有できるモジュールの形式でコードを共有および再利用することが容易になりました。 ただし、多くのソリューションがあり、JavaScriptにネイティブなものはなかったため、ブラウザーでモジュールを使用するには、 Babel 、 Webpack 、Browserifyなどのツールを実装する必要がありました。
複数ファイルアプローチには多くの問題があり、提案されたソリューションは複雑であるため、開発者はモジュラープログラミングアプローチをJavaScript言語に導入することに関心を持っていました。 このため、ECMAScript2015はJavaScriptモジュールの使用をサポートしています。
モジュールは、他のモジュールが使用する機能を提供し、他のモジュールの機能に依存できるようにするためのインターフェイスとして機能するコードのバンドルです。 モジュールはをエクスポートしてコードを提供し、はをインポートして他のコードを使用します。 モジュールは、開発者がコードを再利用できるようにし、多くの開発者が使用できる安定した一貫性のあるインターフェイスを提供し、グローバル名前空間を汚染しないため便利です。
モジュール(ECMAScriptモジュールまたはESモジュールと呼ばれることもあります)がJavaScriptでネイティブに利用できるようになりました。このチュートリアルの残りの部分では、モジュールをコードで使用および実装する方法について説明します。
ネイティブJavaScriptモジュール
JavaScriptのモジュールは、import
およびexport
キーワードを使用します。
import
:別のモジュールからエクスポートされたコードを読み取るために使用されます。export
:他のモジュールにコードを提供するために使用されます。
これを使用する方法を示すために、functions.js
ファイルをモジュールに更新し、関数をエクスポートします。 各関数の前にexport
を追加します。これにより、他のモジュールで使用できるようになります。
次の強調表示されたコードをファイルに追加します。
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
に追加します。
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"
を追加します。 import
またはexport
を使用するコードは、次の属性を使用する必要があります。
...
<script type="module" src="functions.js"></script>
<script type="module" src="script.js"></script>
この時点で、更新を含むページをリロードできるようになり、Webサイトはモジュールを使用するようになります。 ブラウザのサポートは非常に高いですが、 caniuse を使用して、どのブラウザがサポートしているかを確認できます。 ファイルをローカルファイルへの直接リンクとして表示している場合は、次のエラーが発生することに注意してください。
OutputAccess 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ポリシーのため、モジュールはサーバー環境で使用する必要があります。サーバー環境は、 http-server を使用してローカルにセットアップするか、ホスティングプロバイダーを使用してインターネット上でセットアップできます。
モジュールは、いくつかの点で通常のスクリプトとは異なります。
- モジュールは、グローバル(
window
)スコープに何も追加しません。 - モジュールは常に厳密モードです。
- モジュールは1回しか実行されないため、同じファイルに同じモジュールを2回ロードしても効果はありません。
- モジュールにはサーバー環境が必要です。
モジュールは、ブラウザーのサポートと追加機能を強化するためにWebpackなどのバンドラーと一緒に使用されることがよくありますが、ブラウザーで直接使用することもできます。
次に、import
およびexport
構文を使用できるいくつかの方法について説明します。
名前付きエクスポート
前に示したように、export
構文を使用すると、名前でエクスポートされた値を個別にインポートできます。 たとえば、この簡略化されたバージョンのfunctions.js
を考えてみましょう。
export function sum() {}
export function difference() {}
これにより、中括弧を使用してsum
およびdifference
を名前でインポートできます。
import { sum, difference } from './functions.js'
エイリアスを使用して関数の名前を変更することもできます。 これは、同じモジュール内での名前の競合を回避するために行うことができます。 この例では、sum
の名前がadd
に変更され、difference
の名前がsubtract
に変更されます。
import {
sum as add,
difference as subtract
} from './functions.js'
add(1, 2) // 3
ここでadd()
を呼び出すと、sum()
関数の結果が得られます。
*
構文を使用すると、モジュール全体のコンテンツを1つのオブジェクトにインポートできます。 この場合、sum
とdifference
はmathFunctions
オブジェクトのメソッドになります。
import * as mathFunctions from './functions.js'
mathFunctions.sum(1, 2) // 3
mathFunctions.difference(10, 3) // 7
プリミティブ値、関数の式と定義、非同期関数、クラス、およびインスタンス化されたクラスは、識別子があればすべてエクスポートできます。
// Primitive values
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']
// Function expression
export const sum = (x, y) => x + y
// Function definition
export function difference(x, y) {
return x - y
}
// Asynchronous function
export async function getBooks() {}
// Class
export class Book {
constructor(name, author) {
this.name = name
this.author = author
}
}
// Instantiated class
export const book = new Book('Lord of the Rings', 'J. R. R. Tolkien')
これらのエクスポートはすべて正常にインポートできます。 次のセクションで説明するもう1つのタイプのエクスポートは、デフォルトのエクスポートと呼ばれます。
デフォルトのエクスポート
前の例では、複数の名前付きエクスポートをエクスポートし、それらを個別に、または1つのオブジェクトとしてインポートし、各エクスポートをオブジェクトのメソッドとしてインポートしました。 モジュールには、default
キーワードを使用して、デフォルトのエクスポートを含めることもできます。 デフォルトのエクスポートは中括弧でインポートされませんが、名前付き識別子に直接インポートされます。
たとえば、functions.js
ファイルの次の内容を取り上げます。
export default function sum(x, y) {
return x + y
}
script.js
ファイルでは、次のようにデフォルト関数をsum
としてインポートできます。
import sum from './functions.js'
sum(1, 2) // 3
インポート中にデフォルトのエクスポートに名前を付けることができる制限がないため、これは危険な場合があります。 この例では、デフォルトの関数はdifference
としてインポートされますが、実際にはsum
関数です。
import difference from './functions.js'
difference(1, 2) // 3
このため、名前付きエクスポートを使用することがしばしば好まれます。 名前付きエクスポートとは異なり、デフォルトのエクスポートには識別子は必要ありません。プリミティブ値自体または無名関数をデフォルトのエクスポートとして使用できます。 以下は、デフォルトのエクスポートとして使用されるオブジェクトの例です。
export default {
name: 'Lord of the Rings',
author: 'J. R. R. Tolkien',
}
これをbook
として次のようにインポートできます。
import book from './functions.js'
同様に、次の例は、匿名の矢印関数をデフォルトのエクスポートとしてエクスポートする方法を示しています。
export default () => 'This function is anonymous'
これは、次のscript.js
でインポートできます。
import anonymousFunction from './functions.js'
2つの名前付き値とデフォルト値をエクスポートするこのモジュールのように、名前付きエクスポートとデフォルトのエクスポートを一緒に使用できます。
export const length = 10
export const width = 5
export default function perimeter(x, y) {
return 2 * (x + y)
}
これらの変数とデフォルトの関数を次のようにインポートできます。
import calculatePerimeter, { length, width } from './functions.js'
calculatePerimeter(length, width) // 30
これで、デフォルト値と名前付き値の両方がスクリプトで使用できるようになりました。
結論
モジュラープログラミングの設計手法により、コードを個々のコンポーネントに分割して、コードを再利用可能で一貫性のあるものにすると同時に、グローバル名前空間を保護することができます。 モジュールインターフェイスは、import
およびexport
キーワードを使用してネイティブJavaScriptで実装できます。
この記事では、JavaScriptのモジュールの歴史、JavaScriptファイルを複数のトップレベルスクリプトに分割する方法、モジュラーアプローチを使用してそれらのファイルを更新する方法、およびimport
と
JavaScriptのモジュールの詳細については、MozillaDeveloperNetworkのModulesを参照してください。 Node.jsのモジュールを調べたい場合は、Node.jsモジュールの作成方法チュートリアルをお試しください。