この記事は、電子書籍「ECMAScript 6 の概要」から参照されています: https://es6.ruanyifeng.com/
モジュールの構文
1。概要
歴史的に、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');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;
上記のコードの本質は、fs
モジュール全体をロードし (つまり、fs
のすべてのメソッドをロードし)、オブジェクト ( _fs
) を生成し、このオブジェクトから 3 つのメソッドを読み取ることです。この種の読み込みは「実行時読み込み」と呼ばれます。これは、このオブジェクトは実行時にのみ取得できるため、コンパイル時に「静的最適化」を行うことができないためです。
ES6
モジュールはオブジェクトではなく、コマンドによる出力とコマンドによる入力export
が明示的に指定されたコードです。import
// ES6模块
import {
stat, exists, readFile } from 'fs';
上記のコードの本質は、fs
モジュールから 3 つのメソッドをロードし、他のメソッドをロードしないことです。この種の読み込みは「コンパイル時読み込み」または静的読み込みと呼ばれます。つまり、ES6 はコンパイル時にモジュールの読み込みを完了でき、CommonJS モジュールの読み込みより効率的です。もちろん、ES6 モジュール自体はオブジェクトではないため、これにより ES6 モジュール自体を参照できなくなります。
ES6モジュールはコンパイル時に読み込まれるため、静的解析が可能です。これにより、静的解析でしか実現できないマクロや型システムの導入など、JavaScript の構文をさらに拡張することができます。
静的読み込みによってもたらされるさまざまな利点に加えて、ES6 モジュールには次の利点もあります。
- UMD モジュール形式は不要になり、将来的にはサーバーとブラウザが ES6 モジュール形式をサポートする予定です。現在、これはさまざまなツール ライブラリを通じて実現されています。
- 将来的には、新しいブラウザ API はモジュール形式で提供され、ナビゲータ オブジェクトのグローバル変数やプロパティにする必要はなくなります。
- オブジェクトは名前空間 (Math オブジェクトなど) として必要なくなり、将来的にはこれらの関数をモジュールを通じて提供できるようになります。
2.ストリクトモード
use strict
ES6 モジュールは、モジュール ヘッダーに" "; を追加するかどうかに関係なく、自動的に strict モードを採用します。
Strict モードには主に次の制限があります。
- 変数は使用前に宣言する必要があります
- 関数のパラメータに同じ名前の属性を含めることはできません。そうでない場合は、エラーが報告されます。
with
ステートメントは使用できません- 読み取り専用属性に値を割り当てることはできません。割り当てないとエラーが報告されます。
- 接頭辞 0 を使用して 8 進数を表すことはできません。そうでない場合は、エラーが報告されます。
- Undelete 属性は削除できません。削除しないとエラーが報告されます。
- 変数は削除できず
delete prop
、エラーが報告されます。削除できるのは属性のみです。delete global[prop]
eval
外部スコープに変数を導入しませんeval
arguments
再割り当てはできませんarguments
関数パラメータの変更は自動では反映されません- 故障中
arguments.callee
- 故障中
arguments.caller
this
グローバル オブジェクトへのポイントを無効にするfn.caller
およびを使用してfn.arguments
関数呼び出しのスタックを取得することはできません- 予約語(
protected
、static
、 などinterface
)を追加しました。
モジュールは上記の制限に従う必要があります。strict モードがES5
導入されており、 には属さないためES6
、関連ES5
書籍を参照してください。
this
その中でも、の制限には特に注意を払う必要があります。ES6
モジュールでは、トップレベルのthis
ポインタをundefined
トップレベルのコードで使用しないでくださいthis
。
3. エクスポートコマンド
モジュール関数は主に と の 2 つのコマンドで構成されexport
ますimport
。export
コマンドはモジュールの外部インターフェースを指定するために使用され、import
コマンドは他のモジュールが提供する機能を入力するために使用されます。
モジュールは独立したファイルです。このファイル内の変数はすべて外部から取得することはできません。外部からモジュール内の変数を読み取れるようにしたい場合は、export
キーワードを使用して変数を出力する必要があります。export
以下は、コマンドを使用して変数を出力する JS ファイルです。
// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
上記コードはprofile.js
ユーザー情報を保存するファイルです。ES6
コマンドを使ってexport
3つの変数を外部に出力するモジュールと考えてください。
export
上記以外にも書き方があります。
// profile.js
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export {
firstName, lastName, year };
上記のコードはexport
コマンドの後に続き、中括弧を使用して出力する変数のセットを指定します。前の書き方(文の直前)とvar
同等ですが、こちらの書き方が優先されます。こうすることで、スクリプトの最後にどの変数が出力されるのかが一目でわかるからです。
export
コマンドは変数の出力に加えて、関数またはクラス ( class
) を出力することもできます。
export function multiply(x, y) {
return x * y;
};
上記のコードは関数を外部に出力しますmultiply
。
通常、export
出力変数は元の名前ですが、as
キーワードを使用して名前を変更できます。
function v1() {
... }
function v2() {
... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};
上記のコードは、as
キーワードを使用して関数v1
とv2
外部インターフェイスの名前を変更します。名前を変更した後、v2
別の名前で 2 回出力することができます。
特に注意が必要なのは、export
コマンドが外部インターフェイスを指定し、モジュール内の変数と 1 対 1 の対応を確立する必要があることです。
// 报错
export 1;
// 报错
var m = 1;
export m;
外部インターフェイスが提供されていないため、上記の書き込み方法はどちらもエラーを報告します。1 番目の記述方法は直接 1 を出力し、2 番目の記述方法はm
変数を介して 1 を直接出力します。1 は単なる値であり、インターフェイスではありません。正しい書き方は以下の通りです。
// 写法一
export var m = 1;
// 写法二
var m = 1;
export {
m};
// 写法三
var n = 1;
export {
n as m};
上記 3 つの書き方はいずれも正しく、外部インターフェースを指定しますm
。他のスクリプトは、このインターフェイスを通じて値 1 を取得できます。それらの本質は、インターフェイス名とモジュールの内部変数の間に 1 対 1 の対応を確立することです。
同様に、 sum のfunction
出力class
もこの記述方法に従う必要があります。
// 报错
function f() {
}
export f;
// 正确
export function f() {
};
// 正确
function f() {
}
export {
f};
現在、export
コマンドが外部に出力できるインターフェイスは、関数、クラス、var
宣言let
さconst
れた変数の 3 つです。
さらに、export
ステートメント出力のインターフェイスには、対応する値との動的なバインディング関係があります。つまり、このインターフェイスを通じて、モジュール内のリアルタイム値を取得できます。
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
foo
上記のコードは、の値を持つ変数を出力しbar
、500 ミリ秒後に に変更しますbaz
。
これはCommonJS
標準とはまったく異なります。CommonJS
このモジュールは値のキャッシュを出力し、動的な更新はありません。
最後に、export
コマンドは、モジュールの最上位にある限り、モジュール内のどこにでも出現できます。ブロックレベルのスコープにある場合は、import
次のセクションのコマンドと同様にエラーが報告されます。これは、条件付きコード ブロックでは静的な最適化を行うことができず、ES6
モジュールの本来の設計意図に反するためです。
function foo() {
export default 'bar' // SyntaxError
}
foo()
上記のコードでは、export
ステートメントが関数内に配置され、エラーが報告されます。
4.インポートコマンド
コマンドを使用してexport
モジュールの外部インターフェイスを定義した後、他の JS ファイルはimport
コマンドを通じてモジュールをロードできます。
// main.js
import {
firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
上記のコードのコマンドは、ファイルをimport
ロードし、そこから変数を入力するために使用されます。このコマンドは、他のモジュールからインポートされる変数の名前を指定する中括弧のペアを受け入れます。中括弧内の変数名は、インポートされたモジュールの外部インターフェイスの名前 ( ) と同じである必要があります。profile.js
import
profile.js
入力変数の名前を変更する場合は、コマンド内のキーワードをimport
使用して入力変数の名前を変更します。as
import {
lastName as surname } from './profile.js';
import
コマンドの本質は入力インターフェイスであるため、コマンドによって入力される変数はすべて読み取り専用です。つまり、モジュールをロードするスクリプト内でインターフェイスを書き換えることはできません。
import {
a} from './xxx.js'
a = {
}; // Syntax Error : 'a' is read-only;
上記のコードでは、スクリプトは変数 をロードしますが、変数が読み取り専用インターフェイスであるa
ため、再割り当てされるとエラーが報告されます。a
ただし、 がa
オブジェクトの場合、 のa
プロパティをオーバーライドすることができます。
import {
a} from './xxx.js'
a.foo = 'hello'; // 合法操作
上記のコードでは、a
属性を正常に書き換えることができ、他のモジュールも書き換えられた値を読み取ることができます。ただし、この書き方ではエラーチェックが難しいため、入力変数はすべて完全に読み取り専用として扱い、属性を安易に変更しないことを推奨します。
import
以下では、from
モジュール ファイルの場所を指定します。相対パスまたは絶対パスを使用できます。パスがなくモジュール名だけがある場合は、JavaScript エンジンにモジュールの場所を伝えるための構成ファイルが必要です。
import {
myMethod } from 'util';
上記のコードではutil
モジュール ファイル名ですが、パスが含まれていないため、このモジュールの取得方法をエンジンに伝えるように設定する必要があります。
import
このコマンドには昇格効果があり、モジュール全体の先頭に昇格されて最初に実行されることに注意してください。
foo();
import {
foo } from 'my_module';
import
の実行が のfoo
呼び出しよりも早いため、上記のコードはエラーを報告しません。この動作の本質は、import
コードが実行される前のコンパイル段階でコマンドが実行されることです。
静的に実行されるためimport
、式や変数は使用できず、実行時にのみ結果が得られる構文構造です。
// 报错
import {
'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import {
foo } from module;
// 报错
if (x === 1) {
import {
foo } from 'module1';
} else {
import {
foo } from 'module2';
}
上記 3 つの記述方法は、式、変数、if
構造体を使用するため、エラーが報告されます。静的分析段階では、これらの構文は評価できません。
最後に、import
ロードされたモジュールを実行するステートメントなので、次のように記述できます。
import 'lodash';
上記のコードはlodash
モジュールを実行するだけで、値は入力しません。
同じステートメントが複数回実行される場合import
、複数回ではなく 1 回のみ実行されます。
import 'lodash';
import 'lodash';
上記のコードは 2 回ロードされますlodash
が、実行されるのは 1 回だけです。
import {
foo } from 'my_module';
import {
bar } from 'my_module';
// 等同于
import {
foo, bar } from 'my_module';
上記のコードでは、foo
と がbar
2 つのステートメントでロードされていますが、これらは同じmy_module
モジュールに対応します。つまり、import
ステートメントはシングルトン パターンです。
この段階では、Babel トランスコーディングにより、require
CommonJS モジュールのコマンドと ES6 モジュールのコマンドをimport
同じモジュールに記述することができますが、これは行わない方が良いでしょう。これは静的解析フェーズ中に実行されるためimport
、モジュール内で最も早く実行されます。以下のコードでは、期待した結果が得られない可能性があります。
require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';
5. モジュールの全体的なロード
ロードする特定の出力値を指定することに加えて、全体ロードを使用することもできます。つまり、アスタリスク ( *
) を使用してオブジェクトを指定すると、すべての出力値がこのオブジェクトにロードされます。
以下はcircle.js
2 つのメソッドarea
sumを出力するファイルですcircumference
。
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
次に、このモジュールをロードします。
// main.js
import {
area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
上記はロードするメソッドを一つずつ指定する方法でしたが、全体のロード方法は以下のようになります。
import * as circle from './circle';
console.log('圆面积:' + circle.area(4));
console.log('圆周长:' + circle.circumference(14));
モジュールが全体としてロードされるオブジェクト (上記の例circle
) は静的に分析可能である必要があるため、実行時に変更することはできないことに注意してください。以下の書き込み方法は使用できません。
import * as circle from './circle';
// 下面两行都是不允许的
circle.foo = 'hello';
circle.area = function () {
};
6. デフォルトコマンドのエクスポート
前の例からわかるように、import
コマンドを使用するとき、ユーザーはロードする変数または関数の名前を知っている必要があり、そうでない場合はロードできません。ただし、ユーザーは間違いなくすぐに使い始めたいと考えており、モジュールにどのような属性やメソッドがあるかを理解するためにドキュメントを読む気はないかもしれません。
ユーザーに利便性を提供し、ドキュメントを読まずにモジュールをロードできるようにするために、export default
コマンドを使用してモジュールのデフォルト出力を指定します。
// export-default.js
export default function () {
console.log('foo');
}
上記のコードはモジュール ファイルでありexport-default.js
、そのデフォルトの出力は関数です。
他のモジュールがこのモジュールをロードするとき、import
コマンドは匿名関数に任意の名前を指定できます。
// import-default.js
import customName from './export-default';
customName(); // 'foo'
上記のコードのコマンドはimport
、任意の名前の出力メソッドを指すことができますexport-default.js
が、この場合、元のモジュールが出力する関数名を知る必要はありません。import
現時点ではコマンドの後に中括弧が使用されていないことに注意してください。
export default
このコマンドを匿名でない関数の前に使用することもできます。
// export-default.js
export default function foo() {
console.log('foo');
}
// 或者写成
function foo() {
console.log('foo');
}
export default foo;
上記のコードでは、foo
関数の関数名がfoo
モジュール外では無効です。ロード時は無名関数としてロードしたものとみなされます。
デフォルトの出力と通常の出力を比較してみましょう。
// 第一组
export default function crc32() {
// 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() {
// 输出
// ...
};
import {
crc32} from 'crc32'; // 输入
上記のコードを記述するには 2 つの方法があり、最初のグループは をexport default
使用する場合、対応するimport
ステートメントは中括弧を使用する必要がなく、2 番目のグループは をexport default
使用しない場合、対応するimport
ステートメントは中括弧を使用する必要があります。
export default
コマンドは、モジュールのデフォルト出力を指定するために使用されます。明らかに、モジュールはデフォルト出力を 1 つしか持つことができないため、export default
コマンドは 1 回しか使用できません。したがって、import
中括弧はコマンドに一意にのみ対応できるため、コマンドの後に中括弧を追加する必要はありませんexport default
。
基本的に、という名前の変数またはメソッドexport default
が出力されdefault
、システムはそれに任意の名前を付けることができます。したがって、次の記述は有効です。
// modules.js
function add(x, y) {
return x * y;
}
export {
add as default};
// 等同于
// export default add;
// app.js
import {
default as foo } from 'modules';
// 等同于
// import foo from 'modules';
export default
このコマンドは実際には呼び出された変数を出力するだけであるためdefault
、このコマンドの後に変数宣言ステートメントを続けることはできません。
// 正确
export var a = 1;
// 正确
var a = 1;
export default a;
// 错误
export default var a = 1;
上記のコードでは、変数の値を変数に代入するexport default a
ことを意味します。したがって、最後の書き方ではエラーが報告されます。a
default
同様に、export default
コマンドの本質は次の値をdefault
変数に代入することなので、 の後に直接値を書き込むことができますexport default
。
// 正确
export default 42;
// 报错
export 42;
上記のコードでは、前の文で外部インターフェイスが指定されているのに、後の文で報告されるエラーは外部インターフェイスが指定されていないためですdefault
。
このコマンドを使用するとexport default
、lodash モジュールを例として、モジュールに入るのが非常に直感的になります。
import _ from 'lodash';
import
デフォルトのメソッドと他のインターフェイスを 1 つのステートメントで同時に入力したい場合は、次のように記述できます。
import _, {
each, forEach } from 'lodash';
上記のコードに対応するステートメントはexport
次のとおりです。
export default function (obj) {
// ···
}
export function each(obj, iterator, context) {
// ···
}
export {
each as forEach };
上記のコードの最後の行は、forEach
インターフェイスが公開されておりeach
、デフォルトでそのインターフェイスをポイントしていること、つまりforEach
、each
同じメソッドをポイントしていることを意味します。
export default
クラスの出力にも使用できます。
// MyClass.js
export default class {
... }
// main.js
import MyClass from 'MyClass';
let o = new MyClass();
7. エクスポートとインポートの複合記述
モジュール内で、最初に入力し、次に同じモジュールを出力する場合、import
ステートメントをexport
ステートメントと一緒に記述することができます。
export {
foo, bar } from 'my_module';
// 可以简单理解为
import {
foo, bar } from 'my_module';
export {
foo, bar };
上記のコードでは、export
とimport
ステートメントを組み合わせて 1 行に記述することができます。ただし、1 行で記述した後、foo
と は実際には現在のモジュールにインポートされるのではなく、これら 2 つのインターフェイスを外部に転送することと同じになり、現在のモジュールではと を直接bar
使用できなくなることに注意してください。foo
bar
モジュール インターフェイスの名前変更と全体的な出力もこの方法で記述することができます。
// 接口改名
export {
foo as myFoo } from 'my_module';
// 整体输出
export * from 'my_module';
デフォルトのインターフェースは次のように書かれています。
export {
default } from 'foo';
名前付きインターフェースをデフォルトインターフェースに変更する記述方法は以下のとおりです。
export {
es6 as default } from './someModule';
// 等同于
import {
es6 } from './someModule';
export default es6;
同様に、デフォルトのインターフェースの名前を名前付きインターフェースとして変更できます。
export {
default as es6 } from './someModule';
ES2020 より前には、対応する複合記述方法が存在しない一種のimport
ステートメントがありました。
import * as someIdentifier from "someModule";
ES2020ではこの書き方が補われています。
export * as ns from "mod";
// 等同于
import * as ns from "mod";
export {
ns};
8. モジュールの継承
モジュールは相互に継承することもできます。
circleplus
モジュールを継承するモジュールがあるとしますcircle
。
// circleplus.js
export * from 'circle';
export var e = 2.71828182846;
export default function(x) {
return Math.exp(x);
}
上記のコードでは、モジュールのすべてのプロパティとメソッドexport *
を出力することを意味します。このコマンドはモジュールのメソッドを無視するcircle
ことに注意してください。次に、上記のコードはカスタム変数とデフォルトのメソッドを出力します。export *
circle
default
e
このとき、circle
出力する前に属性またはメソッドの名前を変更することもできます。
// circleplus.js
export {
area as circleArea } from 'circle';
上記のコードは、circle
モジュールのarea
メソッドのみが出力され、名前が変更されることを示していますcircleArea
。
上記モジュールのロード方法は以下の通りです。
// main.js
import * as math from 'circleplus';
import exp from 'circleplus';
console.log(exp(math.e));
上記のコードのimport exp
式は、circleplus
モジュールのデフォルト メソッドをexp
メソッドとしてロードします。
9. モジュール間定数
const
宣言された定数は、現在のコード ブロック内でのみ有効です。モジュール間(つまり、複数のファイルにまたがる)定数を設定したい場合、または複数のモジュールで値を共有する場合は、次の記述方法を使用できます。
// constants.js 模块
export const A = 1;
export const B = 3;
export const C = 4;
// test1.js 模块
import * as constants from './constants';
console.log(constants.A); // 1
console.log(constants.B); // 3
// test2.js 模块
import {
A, B} from './constants';
console.log(A); // 1
console.log(B); // 3
使用する定数が多数ある場合は、特別なconstants
ディレクトリを作成し、さまざまな定数を別のファイルに記述して、このディレクトリに保存できます。
// constants/db.js
export const db = {
url: 'http://my.couchdbserver.local:5984',
admin_username: 'admin',
admin_password: 'admin password'
};
// constants/user.js
export const users = ['root', 'admin', 'staff', 'ceo', 'chief', 'moderator'];
次に、これらのファイルによって出力された定数をindex.js
それにマージします。
// constants/index.js
export {
db} from './db';
export {
users} from './users';
使用するときはindex.js
直接読み込むだけです。
// script.js
import {
db, users} from './constants/index';
10. import()関数
導入
前に述べたように、import
コマンドは JavaScript エンジンによって静的に分析され、モジュール内の他のステートメントの前に実行されます (import
コマンドは「バインディング」と呼ばれますが、実際にはより適切です)。したがって、次のコードはエラーを報告します。
// 报错
if (x === 2) {
import MyModual from './myModual';
}
上記のコードでは、エンジンはimport
コンパイル時にステートメントを処理し、if
この時点ではステートメントの分析や実行を行わないため、import
ステートメントをif
コード ブロックに配置することは無意味であるため、実行ではなく構文エラーが報告されます。エラー。つまり、import
とexport
コマンドはモジュールの最上位レベルにのみ存在でき、コード ブロック内 (たとえば、if
コード ブロック内や関数内) 内には存在できません。
このような設計はコンパイラーの効率を向上させるのに役立ちますが、実行時にモジュールをロードできなくなります。構文的に、条件付きロードは不可能です。import
これは、コマンドが Node のメソッドを置き換える場合にrequire
障害となります。require
はランタイム読み込みモジュールであるため、import
このコマンドはrequire
動的読み込み関数を置き換えることはできません。
const path = './' + fileName;
const myModual = require(path);
上記のステートメントは動的ロードであり、require
どのモジュールがロードされるかは実行時にのみわかります。import
コマンドではこれを行うことはできません。
ES2020 提案では、import()
モジュールの動的な読み込みをサポートする機能が導入されています。
import(specifier)
上記のコードでは、import
関数のパラメーターは、specifier
ロードされるモジュールの場所を指定します。import
この関数は、コマンドがimport()
受け入れることができるすべてのパラメーターを受け入れることができます。この 2 つの主な違いは、後者は動的にロードされることです。
import()
オブジェクトを返しますPromise
。以下に例を示します。
const main = document.querySelector('main');
import(`./section-modules/${
someVariable}.js`)
.then(module => {
module.loadPageInto(main);
})
.catch(err => {
main.textContent = err.message;
});
import()
関数は、モジュールだけでなく、モジュール以外のスクリプトでもどこでも使用できます。これは実行時に実行されます。つまり、この文が実行されると、指定されたモジュールがロードされます。また、import()
関数にはロードされたモジュールとの静的な接続関係がありません。これもimport
ステートメントとは異なります。import()
Node.jsrequire()
メソッドと同様に、主な違いは、前者が非同期読み込みであり、後者が同期読み込みであることです。
import()
オブジェクトが返却されるためPromise
、メソッドでハンドラ関数then()
を指定する必要があります。コードの明瞭さを考慮すると、await
このコマンドを使用することをお勧めします。
async function renderWidget() {
const container = document.getElementById('widget');
if (container !== null) {
// 等同于
// import("./widget").then(widget => {
// widget.render(container);
// });
const widget = await import('./widget.js');
widget.render(container);
}
}
renderWidget();
上記の例では、await
コマンドの後に use が続いておりimport()
、対照的なthen()
書き方の方が明らかに簡潔で読みやすくなっています。
アプリケーション
以下に該当する状況をいくつか示しますimport()
。
(1) オンデマンドでロードします。
import()
必要に応じてモジュールをロードできます。
button.addEventListener('click', event => {
import('./dialogBox.js')
.then(dialogBox => {
dialogBox.open();
})
.catch(error => {
/* Error handling */
})
});
上記のコードでは、import()
メソッドはclick
イベント リスニング関数に配置されており、このモジュールはユーザーがボタンをクリックしたときにのみロードされます。
(2) 条件付き負荷
import()
if
コードブロックに配置し、さまざまな状況に応じてさまざまなモジュールをロードできます。
if (condition) {
import('moduleA').then(...);
} else {
import('moduleB').then(...);
}
上記のコードでは、条件が満たされている場合はモジュール A がロードされ、条件が満たされていない場合はモジュール B がロードされます。
(3) 動的モジュールパス
import()
モジュール パスを動的に生成できるようにします。
import(f())
.then(...);
上記のコードでは、f
関数の戻り結果に応じて異なるモジュールがロードされます。
注意点
import()
モジュールが正常にロードされると、モジュールはオブジェクトとして、またthen
メソッドのパラメーターとして使用されます。したがって、オブジェクトの構造化と代入の構文を使用して、出力インターフェイスを取得できます。
import('./myModule.js')
.then(({
export1, export2}) => {
// ...·
});
上記のコードでは、export1
と はexport2
両方ともmyModule.js
の出力インターフェイスであり、分解を通じて取得できます。
モジュールにdefault
出力インターフェイスがある場合は、パラメータを使用して直接取得できます。
import('./myModule.js')
.then(myModule => {
console.log(myModule.default);
});
上記のコードは、名前付き入力の形式でも使用できます。
import('./myModule.js')
.then(({
default: theDefault}) => {
console.log(theDefault);
});
複数のモジュールを同時にロードしたい場合は、以下のような書き方が可能です。
Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
])
.then(([module1, module2, module3]) => {
···
});
import()
関数内でも使用できますasync
。
async function main() {
const myModule = await import('./myModule.js');
const {
export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}
main();
11.インポート.メタ
開発者がモジュールを使用する場合、テンプレート自体に関する情報 (モジュールへのパスなど) を知る必要がある場合があります。ES2020 は、現在のモジュールのメタ情報を返すimport
コマンドにメタ属性を追加します。import.meta
import.meta
モジュール内でのみ使用できます。モジュール外で使用するとエラーが報告されます。
このプロパティは、さまざまなプロパティが現在実行中のスクリプトに関するメタ情報であるオブジェクトを返します。含まれる特定の属性は標準で指定されておらず、各動作環境によって決定されます。一般に、import.meta
少なくとも次の 2 つの属性が存在します。
(1)import.meta.url
import.meta.url
現在のモジュールの URL パスを返します。たとえば、現在のモジュールのメイン ファイルのパスが の場合https://foo.com/main.js
、import.meta.url
このパスが返されます。module にデータ ファイルがある場合はdata.txt
、次のコードを使用してデータ ファイルのパスを取得できます。
new URL('data.txt', import.meta.url)
Node.js 環境では、import.meta.url
常にローカル パス、つまりfile:URL
プロトコル文字列 (例: )が返されることに注意してくださいfile:///home/user/foo.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"
モジュールロードの実装
ES6 モジュールをブラウザと Node.js にロードする方法は、https: //es6.ruanyifeng.com/#docs/module-loaderを参照してください。
プログラミングスタイル
1. ブロックレベルのスコープ
(1) var を置き換えます
ES6 では、変数を宣言するための 2 つの新しいコマンド、let
と が導入されていますconst
。このうち、2 つのセマンティクスは同じで副作用がないため、let
完全に置き換えることができます。var
let
'use strict';
if (true) {
let x = 'hello';
}
for (let i = 0; i < 10; i++) {
console.log(i);
}
上記のコードをvar
置き換えるとlet
、実際には 2 つのグローバル変数が宣言されますが、これは明らかに意図したものではありません。変数は、変数が宣言されているコード ブロック内でのみ有効である必要があり、var
コマンドではこれを行うことはできません。
var
コマンドには変数昇格効果がありますが、let
この問題は発生しません。
'use strict';
if (true) {
console.log(x); // ReferenceError
let x = 'hello';
}
上記のコードでvar
substitution が使用されている場合let
、変数宣言がコード ブロックの先頭に昇格されるためconsole.log
、その行ではエラーは報告されませんが、 が出力されます。undefined
これは、変数が最初に宣言され、後で使用されるという原則に違反します。
var
したがって、このコマンドを使用せず、let
代わりにを使用することをお勧めします。
(2) グローバル定数とスレッドセーフ
let
と の間ではconst
、最初にこれを使用することをお勧めしますconst
。特にグローバル環境では、変数を設定せず、定数のみを設定する必要があります。
const
let
いくつかの理由から、その方が良いでしょう。1 つは、const
プログラムを読む人に、この変数は変更すべきではないことを思い出させるためであり、もう 1 つは、const
関数型プログラミングの考え方に沿ったものであり、操作は値を変更せず、新しい値を作成するだけであり、これも同様です。将来の分散運用に有利; 最後の理由は JavaScript コンパイラーがconst
最適化してくれるので、これを多用するとconst
プログラムの実行効率が向上します つまり、let
との本質的な違いは、const
実際にはコンパイラーの内部処理の違いです。
// bad
var a = 1, b = 2, c = 3;
// good
const a = 1;
const b = 2;
const c = 3;
// best
const [a, b, c] = [1, 2, 3];
const
定数の宣言には 2 つの利点もあります。1 つ目は、コードを読む人がその値を変更すべきではないことがすぐにわかること、2 つ目は、変数値を不用意に変更することによって引き起こされるエラーを防ぐことです。
すべての関数は定数に設定する必要があります。
長期的には、JavaScript はマルチスレッド実装になる可能性があります (Intel の River Trail のようなプロジェクトなど)。let
この時点で表される変数は、単一のスレッドで実行されるコード内にのみ表示される必要があり、複数のスレッドで共有することはできません。これはスレッドの確実な実行に役立ちます。安全性。
2.文字列
静的文字列では常に一重引用符またはバッククォーテーションを使用しますが、二重引用符は使用しません。動的文字列ではバッククォートが使用されます。
// bad
const a = "foobar";
const b = 'foo' + a + 'bar';
// acceptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${
a}bar`;
3. 代入の分割
配列メンバーを使用して変数に値を代入する場合は、分割代入が推奨されます。
const arr = [1, 2, 3, 4];
// bad
const first = arr[0];
const second = arr[1];
// good
const [first, second] = arr;
関数パラメータがオブジェクトのメンバーである場合は、構造化代入が推奨されます。
// bad
function getFullName(user) {
const firstName = user.firstName;
const lastName = user.lastName;
}
// good
function getFullName(obj) {
const {
firstName, lastName } = obj;
}
// best
function getFullName({
firstName, lastName }) {
}
関数が複数の値を返す場合、配列分割代入よりもオブジェクト分割代入が優先して使用されます。これにより、後で戻り値を追加したり、戻り値の順序を変更したりすることが容易になります。
// bad
function processInput(input) {
return [left, right, top, bottom];
}
// good
function processInput(input) {
return {
left, right, top, bottom };
}
const {
left, right } = processInput(input);
4. オブジェクト
オブジェクトが 1 行で定義されている場合、最後のメンバーはカンマで終わりません。複数行で定義されたオブジェクトの場合、最後のメンバーはカンマで終わります。
// bad
const a = {
k1: v1, k2: v2, };
const b = {
k1: v1,
k2: v2
};
// good
const a = {
k1: v1, k2: v2 };
const b = {
k1: v1,
k2: v2,
};
オブジェクトは可能な限り静的である必要があり、一度定義すると、新しい属性を自由に追加することはできません。属性の追加が避けられない場合は、Object.assign
メソッドを使用します。
// bad
const a = {
};
a.x = 3;
// if reshape unavoidable
const a = {
};
Object.assign(a, {
x: 3 });
// good
const a = {
x: null };
a.x = 3;
オブジェクトの属性名が動的である場合は、オブジェクトの作成時に属性式を使用して定義できます。
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
上記のコードでは、obj
オブジェクトの最後の属性名を計算する必要があります。現時点では、属性式を使用し、obj
新しい属性を作成するときにこの属性を他の属性と一緒に定義することをお勧めします。このようにして、すべてのプロパティが 1 か所で定義されます。
さらに、オブジェクトのプロパティとメソッドは、記述しやすくするために、できるだけ簡潔に表現する必要があります。
var ref = 'some value';
// bad
const atom = {
ref: ref,
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
5. アレイ
...
配列をコピーするには、スプレッド演算子 ( ) を使用します。
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
このメソッドを使用して、Array.from
配列のようなオブジェクトを配列に変換します。
const foo = document.querySelectorAll('.foo');
const nodes = Array.from(foo);
6. 機能
即時実行関数はアロー関数の形式で記述できます。
(() => {
console.log('Welcome to the Internet.');
})();
匿名関数がパラメータとして使用される状況では、代わりにアロー関数を使用してみてください。そのほうが簡潔で拘束力があるからですthis
。
// bad
[1, 2, 3].map(function (x) {
return x * x;
});
// good
[1, 2, 3].map((x) => {
return x * x;
});
// best
[1, 2, 3].map(x => x * x);
アロー関数はバインディングを置き換えるので、今後は使用しFunction.prototype.bind
ないでください。self/_this/that
this
// bad
const self = this;
const boundMethod = function(...params) {
return method.apply(self, params);
}
// acceptable
const boundMethod = method.bind(this);
// best
const boundMethod = (...params) => method.apply(this, params);
シンプルで単一行の再利用不可能な関数の場合は、アロー関数を使用することをお勧めします。関数本体が複雑で行数が多い場合は、従来の関数記述方法を使用する必要があります。
すべての設定項目は 1 つのオブジェクトに集中し、最後のパラメータとして配置する必要があります。コードのセマンティクスが低下し、他の設定項目をオブジェクトに追加するのに役立たないため、ブール値をパラメータとして直接使用しないことをお勧めします。未来。
// bad
function divide(a, b, option = false ) {
}
// good
function divide(a, b, {
option = false } = {
}) {
}
関数本体内では変数を使用せずarguments
、代わりにrest
演算子 ( ...
) を使用してください。rest
演算子はパラメーターを取得することを明示的に示しており、arguments
配列のようなオブジェクトであるため、rest
演算子は実数の配列を提供できます。
// bad
function concatenateAll() {
const args = Array.prototype.slice.call(arguments);
return args.join('');
}
// good
function concatenateAll(...args) {
return args.join('');
}
デフォルト値構文を使用して、関数パラメータのデフォルト値を設定します。
// bad
function handleThings(opts) {
opts = opts || {
};
}
// good
function handleThings(opts = {
}) {
// ...
}
7. マップ構造
Object
との区別に注意してくださいMap
。これらは、現実世界のエンティティ オブジェクトをシミュレートする場合にのみ使用されますObject
。key: value
データ構造だけが必要な場合は、 Map
struct を使用します。Map
トラバーサルメカニズムが組み込まれているためです。
let map = new Map(arr);
for (let key of map.keys()) {
console.log(key);
}
for (let value of map.values()) {
console.log(value);
}
for (let item of map.entries()) {
console.log(item[0], item[1]);
}
8. クラス
Class
必要な操作は常にprototype
に置き換えてください。Class
書き方の方が簡潔で分かりやすいからです。
// bad
function Queue(contents = []) {
this._queue = [...contents];
}
Queue.prototype.pop = function() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
// good
class Queue {
constructor(contents = []) {
this._queue = [...contents];
}
pop() {
const value = this._queue[0];
this._queue.splice(0, 1);
return value;
}
}
実装の継承を使用するextends
と、より簡単で、操作を中断するリスクがないためですinstanceof
。
// bad
const inherits = require('inherits');
function PeekableQueue(contents) {
Queue.apply(this, contents);
}
inherits(PeekableQueue, Queue);
PeekableQueue.prototype.peek = function() {
return this._queue[0];
}
// good
class PeekableQueue extends Queue {
peek() {
return this._queue[0];
}
}
9. モジュール
ES6 モジュール構文は JavaScript モジュールを記述する標準的な方法です。私たちは、この記述方法を使用して Node.js の CommonJS 構文を置き換えることを主張します。
まず、import
substitutionを使用しますrequire()
。
// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;
// ES6 的写法
import {
func1, func2 } from 'moduleA';
次に、export
substitution を使用しますmodule.exports
。
// commonJS 的写法
var React = require('react');
var Breadcrumbs = React.createClass({
render() {
return <nav />;
}
});
module.exports = Breadcrumbs;
// ES6 的写法
import React from 'react';
class Breadcrumbs extends React.Component {
render() {
return <nav />;
}
};
export default Breadcrumbs;
モジュールの出力値が 1 つだけの場合は、それを使用しますexport default
。モジュールに複数の出力値がある場合は、出力値の 1 つが特に重要でない限り、使用しないことをお勧めします。つまり、export default
複数の出力値が等しい場合は、export default
通常のものとの併用はしないでくださいexport
。
モジュールがデフォルトで関数をエクスポートする場合、関数名の最初の文字はユーティリティ メソッドであることを示すために小文字にする必要があります。
function makeStyleGuide() {
}
export default makeStyleGuide;
モジュールがデフォルトでオブジェクトを出力する場合、それが構成値オブジェクトであることを示すために、オブジェクト名の最初の文字を大文字にする必要があります。
const StyleGuide = {
es6: {
}
};
export default StyleGuide;
10. ESLintの使用
ESLint は、構文規則とコーディング スタイルのチェック ツールであり、正しい構文と統一されたスタイルのコードが記述されていることを確認するために使用できます。
まず、ESLint をプロジェクトのルート ディレクトリにインストールします。
$ npm install --save-dev eslint
次に、Airbnb 構文ルール、インポート、a11y、および React プラグインをインストールします。
$ npm install --save-dev eslint-config-airbnb
$ npm install --save-dev eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
最後に、プロジェクトのルート ディレクトリに新しいファイルを作成して、.eslintrc
ESLint を構成します。
{
"extends": "eslint-config-airbnb"
}
現在のプロジェクトのコードが事前に設定されたルールに準拠しているかどうかを確認できるようになりました。
index.js
ファイルのコードは以下の通りです。
var unused = 'I have no purpose!';
function greet() {
var message = 'Hello, World!';
console.log(message);
}
greet();
ESLint を使用してこのファイルをチェックすると、エラーが報告されます。
$ npx eslint index.js
index.js
1:1 error Unexpected var, use let or const instead no-var
1:5 error unused is defined but never used no-unused-vars
4:5 error Expected indentation of 2 characters but found 4 indent
4:5 error Unexpected var, use let or const instead no-var
5:5 error Expected indentation of 2 characters but found 4 indent
✖ 5 problems (5 errors, 0 warnings)
上記のコードは、元のファイルに 5 つのエラーがあることを示しています。そのうち 2 つは、var
コマンドは使用すべきではない、let
またはconst
使用する必要がある、1 つは変数が定義されているが使用されていない、他の 2 つは先頭のコマンドであるというものです。行のインデントは、必要な 2 つのスペースではなく、4 つのスペースにインデントされます。