【Reactシリーズ】ES6学習記(4) モジュール、プログラミングスタイル

この記事は、電子書籍「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 strictES6 モジュールは、モジュール ヘッダーに" "; を追加するかどうかに関係なく、自動的に strict モードを採用します。

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

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

モジュールは上記の制限に従う必要があります。strict モードがES5導入されており、 には属さないためES6、関連ES5書籍を参照してください。

thisその中でも、の制限には特に注意を払う必要があります。ES6モジュールでは、トップレベルのthisポインタをundefinedトップレベルのコードで使用しないでくださいthis

3. エクスポートコマンド

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

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

// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;

上記コードはprofile.jsユーザー情報を保存するファイルです。ES6コマンドを使ってexport3つの変数を外部に出力するモジュールと考えてください。

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キーワードを使用して関数v1v2外部インターフェイスの名前を変更します。名前を変更した後、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宣言letconstれた変数の 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.jsimportprofile.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と がbar2 つのステートメントでロードされていますが、これらは同じmy_moduleモジュールに対応します。つまり、importステートメントはシングルトン パターンです。

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

require('core-js/modules/es6.symbol');
require('core-js/modules/es6.promise');
import React from 'React';

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

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

以下はcircle.js2 つのメソッドareasumを出力するファイルです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ことを意味しますしたがって、最後の書き方ではエラーが報告されます。adefault

同様に、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、デフォルトでそのインターフェイスをポイントしていること、つまりforEacheach同じメソッドをポイントしていることを意味します。

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 };

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

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

// 接口改名
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 *circledefaulte

このとき、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コード ブロックに配置することは無意味であるため、実行ではなく構文エラーが報告されます。エラー。つまり、importexportコマンドはモジュールの最上位レベルにのみ存在でき、コード ブロック内 (たとえば、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.jsimport.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完全に置き換えることができます。varlet

'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';
}

上記のコードでvarsubstitution が使用されている場合let変数宣言がコード ブロックの先頭に昇格されるためconsole.log、その行ではエラーは報告されませんが、 が出力されます。undefinedこれは、変数が最初に宣言され、後で使用されるという原則に違反します。

varしたがって、このコマンドを使用せず、let代わりにを使用することをお勧めします。

(2) グローバル定数とスレッドセーフ

letと の間ではconst、最初にこれを使用することをお勧めしますconst。特にグローバル環境では、変数を設定せず、定数のみを設定する必要があります。

constletいくつかの理由から、その方が良いでしょう。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/thatthis

// 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。これらは、現実世界のエンティティ オブジェクトをシミュレートする場合にのみ使用されますObjectkey: valueデータ構造だけが必要な場合は、 Mapstruct を使用します。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 構文を置き換えることを主張します。

まず、importsubstitutionを使用しますrequire()

// CommonJS 的写法
const moduleA = require('moduleA');
const func1 = moduleA.func1;
const func2 = moduleA.func2;

// ES6 的写法
import {
    
     func1, func2 } from 'moduleA';

次に、exportsubstitution を使用します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

最後に、プロジェクトのルート ディレクトリに新しいファイルを作成して、.eslintrcESLint を構成します。

{
    
    
  "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 つのスペースにインデントされます。

Guess you like

Origin blog.csdn.net/lyabc123456/article/details/135406532