モジュールの構文

概要

歴史的に、JavaScript にはモジュール システムがなかったため、大きなプログラムを相互に依存する小さなファイルに分割し、それらを簡単な方法でアセンブルすることができませんでした。 Ruby の「require」、Python の「import」、CSS でさえ「@import」など、他の言語にもこの機能がありますが、JavaScript にはこの点に関するサポートがなく、大規模で複雑なプロジェクトの開発には非常に困難です。 . そのプロジェクトには大きな障害があった。

ES6 より前に、コミュニティはいくつかのモジュール読み込みソリューションを開発しましたが、最も重要なものは CommonJS と AMD です。前者はサーバー用、後者はブラウザ用です。 ES6 は言語標準のレベルでモジュール機能を実装しており、その実装は非常に簡単で、CommonJS および AMD の仕様を完全に置き換えることができ、ブラウザーとサーバーのユニバーサル モジュール ソリューションになります。

ES6 モジュールの設計思想は、モジュールの依存関係や入出力変数をコンパイル時に決定できるように、できる限り静的であることです。 CommonJS モジュールと AMD モジュールは両方とも、実行時にのみこれらのことを決定できます。たとえば、CommonJS モジュールはオブジェクトであるため、入力時にオブジェクトのプロパティを検索する必要があります。

// CommonJS モジュール
let { stat、exists、readfile } = require('fs');

// と同等
let _fs = require('fs');
stat = _fs.stat; とします。
let 存在 = _fs.exists;
readfile = _fs.readfile; とします。

上記のコードの本質は、fs モジュール全体をロードし (つまり、fs のすべてのメソッドをロード)、オブジェクト (_fs) を生成し、このオブジェクトから 3 つのメソッドを読み取ることです。この種の読み込みは「実行時読み込み」と呼ばれます。これは、このオブジェクトは実行時にのみ取得できるため、コンパイル時に「静的最適化」を行うことができないためです。

ES6 モジュールはオブジェクトではなく、出力コードは「export」コマンドで明示的に指定され、「import」コマンドでインポートされます。

// ES6モジュール
import { stat, 存在する, readFile } から 'fs';

上記のコードの本質は、「fs」モジュールから 3 つのメソッドをロードし、他のメソッドをロードしないことです。この種の読み込みは「コンパイル時読み込み」または静的読み込みと呼ばれます。つまり、ES6 はコンパイル時にモジュールの読み込みを完了でき、CommonJS モジュールの読み込みより効率的です。もちろん、ES6 モジュール自体はオブジェクトではないため、これにより ES6 モジュール自体を参照できなくなります。

ES6モジュールはコンパイル時に読み込まれるため、静的解析が可能です。これにより、静的解析でしか実現できないマクロや型システムの導入など、JavaScript の構文をさらに拡張することができます。

静的読み込みによってもたらされるさまざまな利点に加えて、ES6 モジュールには次の利点もあります。

  • 「UMD」モジュール形式は不要になり、将来的にはサーバーとブラウザの両方が ES6 モジュール形式をサポートする予定です。現在、これはさまざまなツール ライブラリを通じて実現されています。
  • 将来的には、新しいブラウザ API がモジュール形式で提供され、グローバル変数や「navigator」オブジェクトのプロパティにする必要がなくなります。
  • オブジェクトは名前空間 (「Math」 オブジェクトなど) として必要なくなりました。将来的には、これらの関数をモジュールを通じて提供できるようになります。

この章では ES6 モジュールの構文を紹介し、次の章ではブラウザと Node.js に ES6 モジュールをロードする方法を説明します。

厳密モード

ES6 モジュールは、モジュール ヘッダーに "use strict"; を追加するかどうかに関係なく、自動的に strict モードを採用します。

Strict モードには主に次の制限があります。

  • 変数は使用前に宣言する必要があります
  • 関数のパラメータに同じ名前の属性を含めることはできません。そうでない場合は、エラーが報告されます。
  • with ステートメントは使用できません
  • 読み取り専用属性に値を割り当てることはできません。割り当てないとエラーが報告されます。
  • 接頭辞 0 を使用して 8 進数を表すことはできません。そうでない場合は、エラーが報告されます。
  • 削除できない属性は削除できません。削除しないとエラーが報告されます。
  • 変数 delete prop は削除できません。エラーが報告されます。削除できるのは属性 delete global[prop] のみです。
  • eval は外部スコープに変数を導入しません
  • evalarguments は再割り当てできません
  • arguments は関数パラメータの変更を自動的に反映しません
  • arguments.calleeは使用できません
  • arguments.callerは使用できません
  • this がグローバル オブジェクトを指すことを無効にします
  • 関数呼び出しのスタックを取得するために fn.callerfn.arguments を使用することはできません
  • 予約語の追加 (「protected」、「static」、「interface」など)

モジュールは上記の制限に従う必要があります。 strict モードは ES5 で導入されたものであり、ES6 には属さないため、本書では詳しく紹介しません。

中でも「this」の制限には特に注意が必要です。 ES6 モジュールでは、トップレベルの thisunknown を指します。つまり、トップレベルのコードでは this を使用すべきではありません。

エクスポートコマンド

モジュール関数は主に exportimport の 2 つのコマンドで構成されます。 export コマンドはモジュールの外部インターフェイスを指定するために使用され、import コマンドは他のモジュールが提供する機能をインポートするために使用されます。

モジュールは独立したファイルです。このファイル内の変数はすべて外部から取得することはできません。モジュール内の変数を外部から読み取れるようにしたい場合は、「export」キーワードを使用して変数を出力する必要があります。以下は、「export」コマンドを使用して変数を出力する JS ファイルです。

// プロフィール.js
エクスポート var firstName = 'マイケル';
エクスポート var lastName = 'ジャクソン';
エクスポート変数の年 = 1958;

上記のコードは、ユーザー情報を保存する profile.js ファイルです。 ES6 はこれをモジュールとして扱い、export コマンドを使用して 3 つの変数を外部にエクスポートします。

上記以外にも「export」の書き方があります。

// プロフィール.js
var firstName = 'マイケル';
var lastName = 'ジャクソン';
var 年 = 1958;

エクスポート { 名、姓、年 };

上記のコードは「export」コマンドの後にあり、中括弧を使用して出力する変数のセットを指定します。先ほどの書き方(var文の直前)と同等ですが、こちらの書き方を優先してください。こうすることで、スクリプトの最後にどの変数が出力されるのかが一目でわかるからです。

変数のエクスポートに加えて、「export」コマンドは関数やクラスもエクスポートできます。

エクスポート関数 multiply(x, y) {
  x * y を返します。
};

上記のコードは関数 multiply を出力します。

通常、export で出力される変数は元の名前ですが、as キーワードを使用して名前を変更できます。

関数 v1() { ... }
関数 v2() { ... }

輸出 {
  v1 を streamV1 として、
  v2 を streamV2 として、
  ストリーム最新バージョンとしての v2
};

上記のコードは、「as」キーワードを使用して、関数「v1」と「v2」の外部インターフェイスの名前を変更します。名前を変更した後、「v2」を異なる名前で 2 回エクスポートできます。

「export」コマンドは外部インターフェイスを指定し、モジュール内の変数と 1 対 1 の対応を確立する必要があることに注意することが重要です。

// エラーを報告する
エクスポート1;

// エラーを報告する
変数m = 1;
エクスポートm;

外部インターフェイスが提供されていないため、上記の書き込み方法はどちらもエラーを報告します。最初の書き方では 1 を直接出力し、2 番目の書き方では変数 'm' を介して直接 1 を出力します。 「1」は単なる値であり、インターフェイスではありません。正しい書き方は以下の通りです。

//書き方その1
エクスポート変数 m = 1;

//書き方2
変数m = 1;
{m} をエクスポートします。

//書き方3
var n = 1;
{n を m} としてエクスポートします。

上記 3 つの書き方はすべて正しく、外部インターフェイス m を指定します。他のスクリプトは、このインターフェイスを通じて値「1」を取得できます。それらの本質は、インターフェイス名とモジュールの内部変数の間に 1 対 1 の対応を確立することです。

同様に、関数やクラスの出力もこの書き方に従う必要があります。

// エラーを報告する
関数 f() {}
fをエクスポートします。

// 正しい
エクスポート関数 f() {};

// 正しい
関数 f() {}
{f} をエクスポートします。

現在、export コマンドは、関数、クラス、var、let、const で宣言された変数の 3 つのインターフェイスをエクスポートできます。

さらに、「export」ステートメントによって出力されるインターフェースは、対応する値と動的なバインディング関係を持ちます。つまり、このインターフェースを通じて、モジュール内のリアルタイム値を取得できます。

エクスポート var foo = 'バー';
setTimeout(() => foo = 'baz', 500);

上記のコードは、値が bar である変数 foo を出力し、500 ミリ秒後に baz に変更します。

これは CommonJS の仕様とはまったく異なります。 CommonJS モジュールは値のキャッシュを出力し、動的更新はありません。詳細については、以下の「モジュールの読み込み実装」セクションを参照してください。

最後に、「export」コマンドは、モジュールの最上位にある限り、モジュール内のどこにでも使用できます。ブロックレベルのスコープにある場合は、次のセクションの「import」コマンドと同様にエラーが報告されます。これは、条件付きコード ブロック内では静的な最適化を行うことができず、ES6 モジュールの本来の設計意図に反するためです。

関数 foo() {
  デフォルトのエクスポート 'bar' // 構文エラー
}
foo()

上記のコードでは、関数内に「export」ステートメントが配置されており、エラーが報告されます。

インポートコマンド

export コマンドを使用してモジュールの外部インターフェイスを定義した後、他の JS ファイルは import コマンドを通じてモジュールをロードできます。

// メイン.js
import { firstName, lastName, year } から './profile.js';

関数 setName(要素) {
  element.textContent = firstName + ' ' + lastName;
}

上記のコードの import コマンドは、profile.js ファイルをロードし、そこから変数を入力するために使用されます。 import コマンドは、他のモジュールからインポートされる変数の名前を指定する一対の中括弧を受け入れます。中括弧内の変数名は、インポートされたモジュール (profile.js) の外部インターフェイスの名前と同じである必要があります。

入力変数の名前を変更したい場合は、「import」コマンドで「as」キーワードを使用して入力変数の名前を変更します。

import { lastName を姓として } from './profile.js';

import コマンドによって入力される変数は、その本質が入力インターフェイスであるため、すべて読み取り専用です。つまり、モジュールをロードするスクリプト内でインターフェイスを書き換えることはできません。

'./xxx.js' から {a} をインポートします

a = {}; // 構文エラー: 'a' は読み取り専用です。

上記のコードでは、スクリプトは変数 a をロードしますが、a は読み取り専用インターフェイスであるため、変数が再割り当てされるとエラーが報告されます。ただし、「a」がオブジェクトの場合は、「a」のプロパティをオーバーライドすることができます。

'./xxx.js' から {a} をインポートします

a.foo = 'hello' // 正当な操作

上記のコードでは、「a」の属性を正常に書き換えることができ、他のモジュールも書き換えられた値を読み取ることができます。ただし、この書き方ではエラーをチェックするのが困難です。すべての入力変数を完全に読み取り専用として扱い、その属性を簡単に変更しないことをお勧めします。

「import」の後の「from」はモジュール ファイルの場所を指定します。相対パスまたは絶対パスを使用できます。パスがなくモジュール名だけがある場合は、JavaScript エンジンにモジュールの場所を伝える構成ファイルが必要です。

import { myMethod } から 'util';

上記のコードでは、「util」はモジュール ファイル名です。パスが含まれていないため、このモジュールの取得方法をエンジンに伝えるように設定する必要があります。

「import」コマンドには昇格効果があり、モジュール全体の先頭に昇格して最初に実行されることに注意してください。

foo();

'my_module' から { foo } をインポートします。

上記のコードは、foo が呼び出される前に import が実行されるため、エラーは報告されません。この動作の本質は、コードが実行される前のコンパイル段階で「import」コマンドが実行されることです。

「import」は静的に実行されるため、式や変数は使用できません。これらは実行時にのみ結果を取得できる構文構造です。

// エラーを報告する
import { 'f' + 'oo' } から 'my_module';

// エラーを報告する
let module = 'my_module';
モジュールから { foo } をインポートします。

// エラーを報告する
if (x === 1) {
  'module1' から { foo } をインポートします。
} それ以外 {
  'module2' から { foo } をインポートします。
}

上記の 3 つの記述方法は、式、変数、および if 構造を使用するため、エラーが報告されます。静的分析段階では、これらの構文は評価できません。

最後にimport文でロードしたモジュールを実行するので以下のように記述できます。

'lodash' をインポートします。

上記のコードは「lodash」モジュールを実行するだけで、値は入力しません。

同じ import ステートメントが複数回実行された場合、複数回ではなく 1 回だけ実行されます。

'lodash' をインポートします。
'lodash' をインポートします。

上記のコードは lodash を 2 回ロードしますが、実行されるのは 1 回だけです。

'my_module' から { foo } をインポートします。
'my_module' から { bar } をインポートします。

// と同等
import { foo, bar } から 'my_module';

上記のコードでは、foobar が 2 つのステートメントでロードされていますが、これらは同じ my_module モジュールに対応しています。言い換えれば、import ステートメントはシングルトン モードです。

この段階では、Babel トランスコーディングにより、CommonJS モジュールの require コマンドと ES6 モジュールの import コマンドを同じモジュール内に記述することができますが、これは行わない方がよいでしょう。 「import」は静的解析フェーズ中に実行されるため、モジュール内で最も早く実行されます。以下のコードでは、期待した結果が得られない可能性があります。

require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
「React」から React をインポートします。

モジュールの全体的なロード

ロードする特定の出力値を指定することに加えて、全体的なロードを使用することもできます。つまり、アスタリスク (*) を使用してオブジェクトを指定すると、すべての出力値がこのオブジェクトにロードされます。

以下は 2 つのメソッド areacircumference を出力する circle.js ファイルです。

// サークル.js

エクスポート関数のエリア(半径) {
  Math.PI * 半径 * 半径を返します。
}

エクスポート関数の円周(半径) {
  2 * Math.PI * 半径を返します。
}

次に、このモジュールをロードします。

// メイン.js

import { 面積, 円周 } from './circle';

console.log('円のエリア: ' + エリア(4));
console.log('円周: ' + 円周(14));

上記はロードするメソッドを一つずつ指定する方法で、全体のロード方法は以下の通りです。

* を「./circle」から円としてインポートします。

console.log('サークルエリア: ' +circle.area(4));
console.log('円周: ' +circumference(14));

モジュールが全体としてロードされるオブジェクト (上の例では「circle」) は静的に分析可能である必要があるため、実行時に変更できないことに注意してください。以下の書き込み方法は使用できません。

* を「./circle」から円としてインポートします。

// 以下の 2 行は使用できません
Circle.foo = 'こんにちは';
サークル.エリア = 関数 () {};

デフォルトのコマンドをエクスポートする

前の例からわかるように、「import」コマンドを使用する場合、ユーザーはロードする変数または関数の名前を知っている必要があり、そうでない場合はロードできません。ただし、ユーザーは間違いなくすぐに使い始めたいと考えており、モジュールにどのような属性やメソッドがあるかを理解するためにドキュメントを読む気はないかもしれません。

ユーザーに利便性を提供し、ドキュメントを読まずにモジュールをロードできるようにするために、「exportdefault」コマンドを使用してモジュールのデフォルト出力を指定します。

// エクスポート-デフォルト.js
デフォルト関数をエクスポート () {
  console.log('foo');
}

上記のコードはモジュール ファイル export-default.js であり、そのデフォルトの出力は関数です。

他のモジュールがこのモジュールをロードするとき、import コマンドは匿名関数に任意の名前を指定できます。

// インポート-デフォルト.js
'./export-default' からカスタム名をインポートします。
カスタム名(); // 'foo'

上記のコードの import コマンドは、export-default.js 出力メソッドを指すために任意の名前を使用できます。この場合、元のモジュールによって出力された関数名を知る必要はありません。 「import」コマンドの後には中括弧が使用されないことに注意してください。

「exportdefault」コマンドは、非匿名関数の前に使用することもできます。

// エクスポート-デフォルト.js
デフォルト関数 foo() をエクスポート {
  console.log('foo');
}

// または次のように記述されます

関数 foo() {
  console.log('foo');
}

デフォルトの foo をエクスポートします。

上記コードでは、「foo」関数の関数名「foo」はモジュール外では無効です。ロード時は無名関数としてロードしたものとみなされます。

デフォルトの出力と通常の出力を比較してみましょう。

// 最初のグループ
デフォルト関数 crc32() をエクスポート { // 出力
  // ...
}

'crc32' から crc32 をインポートします // 入力

// 2番目のグループ
エクスポート関数 crc32() { // 出力
  // ...
};

import {crc32} from 'crc32'; // 入力

上記のコードの記述方法には 2 つのグループがあります。最初のグループは、「exportdefault」を使用する場合であり、対応する「import」ステートメントは中括弧を使用する必要がありません。2 番目のグループは、「exportdefault」が使用されない場合です。対応する import ステートメントを使用する必要があります。

「exportdefault」コマンドは、モジュールのデフォルト出力を指定するために使用されます。明らかに、モジュールはデフォルト出力を 1 つだけ持つことができるため、「export default」コマンドは 1 回しか使用できません。したがって、import コマンドの後に中括弧を追加する必要はありません。これは、exportdefault コマンドに一意に対応することしかできないためです。

基本的に、「デフォルトのエクスポート」は、「デフォルト」という名前の変数またはメソッドをエクスポートすることです。その後、システムはそれに任意の名前を付けることができます。したがって、次の記述は有効です。

// モジュール.js
関数 add(x, y) {
  x * y を返します。
}
エクスポート {デフォルトとして追加};
// と同等
// デフォルトの追加をエクスポート;

// app.js
import {default as foo } from 'モジュール';
// と同等
// foo を「モジュール」からインポートします。

これはまさに、「exportdefault」コマンドは実際には「default」という変数を出力するだけなので、このコマンドの後に変数宣言文を続けることはできないからです。

// 正しい
エクスポート変数 a = 1;

// 正しい
変数 a = 1;
デフォルトをエクスポートします。

// 間違い
デフォルトの変数 a = 1 をエクスポートします。

上記のコードで、「export default a」の意味は、変数「a」の値を変数「default」に代入することです。したがって、最後の書き方ではエラーが報告されます。

同様に、「exportdefault」コマンドの本質は「default」変数に後続の値を代入することであるため、「exportdefault」の後に値を直接書き込むことができます。

// 正しい
エクスポートのデフォルトは 42// エラーを報告する
輸出42;

上記のコードでは、前の文では外部インターフェースが「デフォルト」として指定されているのに対し、後の文で報告されるエラーは外部インターフェースが指定されていないためです。

「exportdefault」コマンドを使用すると、lodash モジュールを例に挙げると、非常に直感的にモジュールを入力できます。

'lodash' から _ をインポートします。

デフォルトのメソッドと他のインターフェースの両方を 1 つの import ステートメントに入力したい場合は、次のように記述できます。

import _, { each, forEach } から 'lodash';

上記のコードに対応する「export」ステートメントは次のとおりです。

デフォルト関数をエクスポート (obj) {
  //···
}

エクスポート関数 each(obj, イテレータ, コンテキスト) {
  //···
}

エクスポート {それぞれ forEach };

上記のコードの最後の行は、forEach インターフェースが公開され、デフォルトで each インターフェースを指すこと、つまり、forEacheach が同じメソッドを指すことを意味します。

exportdefault を使用してクラスをエクスポートすることもできます。

//MyClass.js
デフォルト クラスをエクスポート { ... }

// メイン.jsMyClass」から MyClass をインポートします。
let o = new MyClass();

エクスポートとインポートの複合記述方法

モジュール内で、同じモジュールが最初にインポートされてから出力される場合、「import」ステートメントは「export」ステートメントと一緒に記述することができます。

'my_module' から { foo, bar } をエクスポートします。

// 単純に次のように理解できます
'my_module' から { foo, bar } をインポートします。
エクスポート { foo, bar };

上記のコードでは、export ステートメントと import ステートメントを組み合わせて 1 行に記述することができます。ただし、これを 1 行で記述した後、foobar は実際には現在のモジュールにインポートされるのではなく、これら 2 つのインターフェイスを外部に転送することと同等であるため、現在のモジュールはインポートできなくなることに注意してください。 foobar を直接使用します。

モジュール インターフェイスの名前変更と全体的な出力もこの方法で記述することができます。

// インターフェースの名前を変更します
'my_module' から { foo as myFoo } をエクスポートします。

// 全体的な出力
エクスポート * 'my_module' から;

デフォルトのインターフェースは次のように書かれています。

'foo' から { デフォルト } をエクスポートします。

名前付きインターフェースをデフォルトインターフェースに変更する記述方法は以下のとおりです。

'./someModule' から { es6 をデフォルトとしてエクスポート };

// と同等
import { es6 } から './someModule';
デフォルトのes6をエクスポートします。

同様に、デフォルトのインターフェースの名前を名前付きインターフェースとして変更できます。

'./someModule' からエクスポート { デフォルトとして es6 };

ES2020 より前には、「import」ステートメントがありましたが、これには対応する複合記述メソッドがありませんでした。

import * as someIdentifier from "someModule";

ES2020 でこの書き方を追加しました。

"mod" から * を ns としてエクスポートします。

// と同等
import * as ns from "mod";
{ns} をエクスポートします。

モジュールの継承

モジュールは相互に継承することもできます。

「circle」モジュールを継承する「circleplus」モジュールがあるとします。

// サークルプラス.js

'circle' から * をエクスポートします。
エクスポート変数 = 2.71828182846;
デフォルト関数をエクスポート(x) {
  Math.exp(x) を返します。
}

上記のコードの export * は、circle モジュールのすべてのプロパティとメソッドをエクスポートすることを意味します。 export * コマンドは、circle モジュールの default メソッドを無視することに注意してください。次に、上記のコードは、カスタマイズされた e 変数とデフォルトのメソッドを出力します。

このとき、circleの属性やメソッドの名前を変更して出力することもできます。

// サークルプラス.js

{ エリアを CircleArea としてエクスポート } from 'circle';

上記のコードは、「circle」モジュールの「area」メソッドのみが出力され、「circleArea」という名前に変更されることを示しています。

上記モジュールのロード方法は以下の通りです。

// メイン.js

import * as math from 'circleplus';
「circleplus」からexpをインポートします。
console.log(exp(math.e));

上記のコードの import exp は、circleplus モジュールのデフォルト メソッドが exp メソッドとしてロードされることを意味します。

モジュール間の定数

本書で const コマンドを紹介するとき、const で宣言された定数は現在のコード ブロック内でのみ有効であると述べられています。モジュール間(つまり、複数のファイルにまたがる)定数を設定したい場合、または複数のモジュールで値を共有する場合は、次の記述方法を使用できます。

// constants.js モジュール
エクスポート定数 A = 1;
エクスポート定数 B = 3;
エクスポート定数 C = 4;

// test1.jsモジュール
import * を './constants' から定数としてインポートします。
console.log(constants.A); // 1
console.log(constants.B); // 3

// test2.jsモジュール
{A, B} を './constants' からインポートします。
コンソール.log(A); // 1
コンソール.log(B); // 3

多くの定数を使用したい場合は、特別な constants ディレクトリを作成し、さまざまな定数を別のファイルに記述して、このディレクトリに保存します。

// 定数/db.js
エクスポート const db = {
  URL: 'http://my.couchdbserver.local:5984',
  管理者ユーザー名: '管理者',
  admin_password: '管理者パスワード'
};

// 定数/user.js
import const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];

次に、これらのファイルによって出力された定数を index.js にマージします。

// 定数/index.js
'./db' から {db} をエクスポートします。
{users} を './users' からエクスポートします。

使用する場合は、「index.js」を直接読み込むだけです。

// スクリプト.js
{db, users} を './constants/index' からインポートします。

輸入()

導入

前に述べたように、「import」コマンドは JavaScript エンジンによって静的に分析され、モジュール内の他のステートメントの前に実行されます (「import」コマンドは「接続」バインディングと呼ばれますが、実際にはこれがより適切です)。したがって、次のコードはエラーを報告します。

// エラーを報告する
if (x === 2) {
  MyModual'./myModual' からインポートします。
}

上記のコードでは、エンジンはコンパイル時に import ステートメントを処理しますが、この時点では if ステートメントの解析や実行は行わないため、if コード内に import ステートメントを配置しても意味がありません。ブロックされるため、実行時エラーではなく構文エラーが報告されます。言い換えれば、「import」コマンドと「export」コマンドは、コード ブロック内 (たとえば、「if」コード ブロック内や関数内) ではなく、モジュールの最上位レベルにのみ配置できます。

このような設計はコンパイラーの効率を向上させるのに役立ちますが、実行時にモジュールをロードできなくなります。構文的に、条件付きロードは不可能です。これにより、「import」コマンドがノードの「require」メソッドを置き換える場合に障害が発生します。 require は実行時にロードされるモジュールであるため、import コマンドは require の動的ロード関数を置き換えることはできません。

const パス = './' + ファイル名;
const myModual = require(パス);

上記のステートメントは動的ロードです。どのモジュールが「require」ロードされるかは、実行時にのみわかります。 「import」コマンドではこれを行うことはできません。

ES2020 プロポーザル では、モジュールの動的ロードをサポートする import() 関数が導入されています。

インポート(指定子)

上記のコードでは、import 関数のパラメータ specifier は、ロードされるモジュールの場所を指定します。 「import」コマンドで受け入れられるパラメータと「import()」関数で受け入れられるパラメータの主な違いは、後者が動的にロードされることです。

import() は Promise オブジェクトを返します。以下に例を示します。

const main = document.querySelector('main');

import(`./section-modules/${someVariable}.js`)
  .then(モジュール => {
    module.loadPageInto(main);
  })
  .catch(err => {
    main.textContent = err.message;
  });

import() 関数は、モジュールだけでなく、モジュール以外のスクリプトでもどこでも使用できます。これは実行時に実行されます。つまり、この文が実行されると、指定されたモジュールがロードされます。さらに、import() 関数にはロードされたモジュールとの静的な接続関係がありません。これも import ステートメントとは異なります。 import() は Node.js の require() メソッドに似ていますが、主な違いは、前者が非同期読み込みであり、後者が同期読み込みであることです。

import() は Promise を返すので オブジェクトなので、処理関数を指定するには then() メソッドを使用する必要があります。コードの明瞭さを考慮すると、「await」コマンドを使用することをお勧めします。

非同期関数 renderWidget() {
  const コンテナ = document.getElementById('ウィジェット');
  if (コンテナ !== null) {
    // と同等
    // import("./ウィジェット").then(ウィジェット => {
    // ウィジェット.render(コンテナ);
    // });
    const widget = await import('./widget.js');
    ウィジェット.レンダー(コンテナ);
  }
}

renderWidget();

上の例では、await コマンドの後に import() が続きます。これは明らかに then() の書き方よりも簡潔で読みやすいです。

該当する機会

以下は、import() が適用されるいくつかの状況です。

(1) オンデマンドでロードします。

import() は必要に応じてモジュールをロードできます。

button.addEventListener('クリック', イベント => {
  import('./dialogBox.js')
  .then(dialogBox => {
    ダイアログボックス.open();
  })
  .catch(エラー => {
    /* エラー処理 */
  })
});

上記のコードでは、import() メソッドが click イベントの listen 関数に配置されており、このモジュールはユーザーがボタンをクリックしたときにのみロードされます。

(2) 条件付き負荷

「import()」を「if」コードブロックに配置して、さまざまな状況に応じてさまざまなモジュールをロードできます。

if (条件) {
  import('moduleA').then(...);
} それ以外 {
  import('moduleB').then(...);
}

上記のコードでは、条件が満たされている場合はモジュール A がロードされ、条件が満たされていない場合はモジュール B がロードされます。

(3) 動的モジュールパス

import() を使用すると、モジュール パスを動的に生成できます。

インポート(f())
。それから(...);

上記のコードでは、関数 f の戻り結果に基づいてさまざまなモジュールがロードされます。

注記

import() がモジュールを正常にロードすると、モジュールはオブジェクトとして使用され、then メソッドのパラメータとして使用されます。したがって、オブジェクトの構造化と代入の構文を使用して、出力インターフェイスを取得できます。

import('./myModule.js')
.then(({エクスポート1, エクスポート2}) => {
  // ...·
});

上記のコードでは、export1export2 は両方とも myModule.js の出力インターフェイスであり、分解を通じて取得できます。

モジュールに「デフォルト」出力インターフェースがある場合、パラメータを使用して直接取得できます。

import('./myModule.js')
.then(myModule => {
  console.log(myModule.default);
});

上記のコードは、名前付き入力の形式でも使用できます。

import('./myModule.js')
.then(({デフォルト: theDefault}) => {
  console.log(デフォルト);
});

複数のモジュールを同時に読み込みたい場合は、以下のような書き方が可能です。

Promise.all([
  import('./module1.js'),
  import('./module2.js'),
  import('./module3.js'),
])
.then(([モジュール1, モジュール2, モジュール3]) => {
   ・・・
});

import() は非同期関数でも使用できます。

非同期関数 main() {
  const myModule = await import('./myModule.js');
  const {export1, export2} = await import('./myModule.js');
  const [モジュール1, モジュール2, モジュール3] =
    Promise.all([
      import('./module1.js'),
      import('./module2.js'),
      import('./module3.js'),
    ]);
}
主要();

import.meta

開発者がモジュールを使用する場合、テンプレート自体に関する情報 (モジュールへのパスなど) を知る必要がある場合があります。 ES2020 現在のモジュールのメタ情報を返すためのメタ属性 import.meta を import コマンドに追加しました。

「import.meta」はモジュール内でのみ使用できます。モジュール外で使用するとエラーが報告されます。

このプロパティは、さまざまなプロパティが現在実行中のスクリプトに関するメタ情報であるオブジェクトを返します。含まれる特定の属性は標準で指定されておらず、各動作環境によって決定されます。一般に、「import.meta」には少なくとも次の 2 つの属性が含まれます。

(1)import.meta.url

import.meta.url は現在のモジュールの URL パスを返します。たとえば、現在のモジュールのメイン ファイルのパスが https://foo.com/main.js である場合、import.meta.url はこのパスを返します。モジュール内に別のデータ ファイル「data.txt」がある場合は、次のコードを使用してこのデータ ファイルのパスを取得できます。

新しい URL('data.txt', import.meta.url)

Node.js 環境では、import.meta.url は常にローカル パス、つまり file:///home/user/foo などの file:URL プロトコルの文字列を返すことに注意してください。 js

(2)import.meta.script要素

import.meta.scriptElement は、モジュールをロードする <script> 要素を返すブラウザ固有のメタ属性であり、document.currentScript プロパティと同等です。

// HTMLコードは
// <script type="module" src="my-module.js" data-foo="abc"></script>

// my-module.js は内部的に次のコードを実行します
import.meta.scriptElement.dataset.foo
// "ABC"

(3) その他

Deno は、CommonJS モジュール システムの __filename および __dirname プロパティに対応する import.meta.filename および import.meta.dirname プロパティもサポートするようになりました。

  • import.meta.filename: 現在のモジュール ファイルの絶対パス。
  • import.meta.dirname: 現在のモジュール ファイルのディレクトリへの絶対パス。

どちらのプロパティも、現在のプラットフォームに適したパス区切り文字を提供します。たとえば、Linux システムは「/dev/my_module.ts」を返し、Windows システムは「C:\dev\my_module.ts」を返します。

どちらのプロパティも、ローカル モジュールだけでなくリモート モジュールでも使用できます。


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0