最新の提案

この章では、まだ規格に組み込まれていない有望な最近の提案をいくつか紹介します。

式を実行します

本質的に、ブロックレベルのスコープは、値を返さずに複数の操作をカプセル化するステートメントです。

{
  t = f(); とします。
  t = t * t + 1;
}

上記のコードでは、ブロックレベルのスコープによって 2 つのステートメントが一緒にカプセル化されます。ただし、「t」がグローバル変数でない限り、ブロックレベルのスコープは値を返さないため、ブロックレベルのスコープの外で「t」の値を取得する方法はありません。

提案 が追加されました。これにより、ブロックレベルのスコープを式に変換できるようになります。これは、値を返すことができることを意味します。 do をブロックレベルのスコープの前に追加すると、それが do 式になり、内部で実行された最後の式の値が返されます。

x = を実行しましょう {
  t = f(); とします。
  t * t + 1;
};

上記のコードでは、変数 x はブロック スコープ全体 (t * t + 1) の戻り値を取得します。

「do」式のロジックは非常に単純です。カプセル化されたものはすべて返されます。

// <式> と同等
do { <式> }

// <ステートメント> と同等
do { <ステートメント> }

「do」式の利点は、複数のステートメントをカプセル化できるため、プログラムがよりモジュール化され、レゴ ブロックのようにパーツごとに組み立てられることです。

x = を実行しましょう {
  if (foo()) { f() }
  else if (bar()) { g() }
  else { h() }
};

上記のコードの本質は、関数 foo の実行結果に基づいてさまざまな関数を呼び出し、戻り結果を変数 x に代入することです。 「do」表現を使用すると、この操作の意図が非常に簡潔かつ明確に表現されます。さらに、「do」ブロックレベルのスコープは別のスコープを提供し、内部操作をグローバル スコープから分離できます。

「do」式は JSX 構文で非常に簡単に使用できることは言及する価値があります。

戻る (
  <ナビ>
    <ホーム />
    {
      する {
        if (ログイン) {
          <ログアウトボタン />
        } それ以外 {
          <ログインボタン />
        }
      }
    }
  </nav>
)

上記のコードでは、do 式を使用しない場合、三項判定演算子 (?:) のみを使用できます。その場合、判定ロジックが複雑になってしまうと、コードが非常に読みにくくなってしまいます。

式をスローします

JavaScript 構文では、「throw」はエラーをスローするために使用されるコマンドであり、式では使用できないと規定されています。

// エラーを報告する
console.log(新しいエラー()をスロー);

上記のコードでは、console.log のパラメータは式でなければなりません。それが throw ステートメントの場合、エラーが報告されます。

現在、式で throw を使用できるようにする 提案 があります。

//パラメータのデフォルト値
function save(filename = throw new TypeError("引数が必要です")) {
}

// アロー関数の戻り値
lint(ast, {
  with: () => throw new Error("with ステートメントの使用は避けてください。")
});

// 条件式
関数 getEncoder(エンコーディング) {
  const encoder = エンコーディング === "utf8" ?
    新しい UTF8Encoder() :
    エンコーディング === "utf16le" ?
      新しい UTF16Encoder(false):
      エンコーディング === "utf16be" ?
        新しい UTF16Encoder(true) :
        throw new Error("サポートされていないエンコーディング");
}

// 論理式
クラス製品 {
  getid() {
    this._id を返します。
  }
  ID(値)を設定 {
    this._id = 値 || throw new Error("無効な値");
  }
}

上記のコードでは、式の中に「throw」が現れています。

構文的には、「throw」式の「throw」はコマンドではなくなり、演算子になります。 throw コマンドとの混乱を避けるために、行の先頭にある throw は常に throw 式ではなく throw ステートメントとして解釈されることが規定されています。

関数の部分的な実行

文法

マルチパラメータ関数は、1 つ以上のパラメータをバインドして新しい関数を返す必要がある場合があります。

関数 add(x, y) { return x + y }
関数 add7(x) { 戻り値 x + 7 }

上記のコードでは、add7 関数は実際には add 関数の特別なバージョンであり、パラメータを 7 にバインドすることで、add から add7 を取得できます。

// バインドメソッド
const add7 = add.bind(null, 7);

// アロー関数
const add7 = x => add(x, 7);

上記 2 つの書き方は多少冗長です。中でも、bind メソッドの制限は、this を提供する必要があり、前から後ろに 1 つずつパラメータをバインドすることしかできず、先頭以外のパラメータのみをバインドすることはできません。

パラメーターをバインドして新しい関数を返すことを容易にする 提案書 が追加されました。これを関数の部分適用といいます。

const add = (x, y) => x + y;
const addOne = add(1, ?);

const maxGreaterThanZero = Math.max(0, ...);

新しい提案によると、「?」は単一パラメータのプレースホルダであり、「...」は複数のパラメータのプレースホルダです。次の形式はすべて、関数の部分的な実行です。

f(x, ?)
f(x, ...)
f(?, x)
f(...,x)
f(?, x, ?)
f(..., x, ...)

?... は関数呼び出しでのみ使用でき、新しい関数を返します。

const g = f(?, 1, ...);
// と同等
const g = (x, ...y) => f(x, 1, ...y);

関数の部分的な実行はオブジェクト メソッドでも使用できます。

obj = { にします
  f(x, y) { 戻り値 x + y },
};

const g = obj.f(?, 3);
g(1) // 4

注記

関数の部分的な実行については、特別な考慮事項がいくつかあります。

(1) 関数の実行の一部は元の関数に基づいています。元の関数が変更された場合、部分的な実行によって生成された新しい関数には、その変更がすぐに反映されます。

f = (x, y) => x + y とします。

const g = f(?, 3);
g(1); // 4

// 関数 f を置き換えます
f = (x, y) => x * y;

g(1); // 3

上記のコードでは、関数が定義されている部分が実行された後、元の関数を置き換えると、すぐに新しい関数に影響します。

(2) あらかじめ指定した値が式の場合、式は定義時に評価されず、呼び出されるたびに評価されます。

a = 3 とします。
const f = (x, y) => x + y;

const g = f(?, a);
g(1); // 4

// a の値を変更します
a = 10;
g(1); // 11

上記のコードでは、あらかじめ与えられたパラメータは変数「a」なので、関数「g」が呼び出されるたびに「a」が評価されます。

(3) 新しい関数にプレースホルダーの数より多くのパラメーターがある場合、余分なパラメーターは無視されます。

const f = (x, ...y) => [x, ...y];
const g = f(?, 1);
g(2, 3, 4); // [2, 1]

上記のコードでは、関数 g にはプレースホルダーが 1 つだけあります。これは、パラメーターを 1 つだけ受け入れることができ、余分なパラメーターは無視されることを意味します。

以下のように書くとパラメータが冗長でも問題ありません。

const f = (x, ...y) => [x, ...y];
const g = f(?, 1, ...);
g(2, 3, 4) // [2, 1, 3, 4];

(4) ... は 1 回だけ収集されます。関数の実行の一部で複数の ... が使用された場合、それぞれの ... の値は同じになります。

const f = (...x) => x;
const g = f(..., 9, ...);
g(1, 2, 3) // [1, 2, 3, 9, 1, 2, 3];

上記のコードでは、g が 2 つの ... プレースホルダーを定義しています。実際に実行すると、それらの値は同じになります。

パイプライン演算子

Unix オペレーティング システムには、前の操作の値を次の操作に渡すことができるパイプライン メカニズムがあります。このメカニズムは非常に便利で、単純な操作を複雑な操作に組み合わせることができます。多くの言語にはパイプライン実装があり、JavaScript にもパイプライン メカニズムを持たせるための 提案書 が存在します。

JavaScript パイプは演算子であり、「|>」として記述されます。左側に式、右側に関数があります。パイプライン演算子は、評価のために左側の式の値を右側の関数に渡します。

x |> f
// と同等
f(x)

パイプライン演算子の最大の利点は、入れ子になった関数を左から右へのチェーン式として記述できることです。

関数 doubleSay (str) {
  戻り値 str + ", " + str;
}

関数大文字化 (str) {
  str[0].toUpperCase() + str.substring(1) を返します。
}

関数の叫び声 (str) {
  str + '!' を返します。
}

上記は 3 つの簡単な関数です。ネスト実行したい場合、従来の記述方法とパイプラインの記述方法は以下のとおりです。

//伝統的な書き方
exclaim(capitalize(doubleSay('hello')))
// 「こんにちは、こんにちは!」

// パイプの書き方
'こんにちは'
  |> ダブルセイ
  |> 大文字にする
  |> 叫ぶ
// 「こんにちは、こんにちは!」

パイプ演算子は単一の値のみを渡すことができます。つまり、その右側の関数は単一引数の関数である必要があります。複数パラメーターの関数の場合は、カリー化して単一パラメーターのバージョンに変更する必要があります。

関数 double (x) { 戻り値 x + x }
関数 add (x, y) { return x + y }

let person = { スコア: 25 };
人物.スコア
  |> ダブル
  |> (_ => add(7, _))
// 57

上記のコードでは、「add」関数には 2 つのパラメータが必要です。ただし、パイプ演算子は 1 つの値しか渡せないため、事前に別のパラメーターを指定し、単一パラメーターのアロー関数 _ => add(7, _) に変更する必要があります。この関数のアンダースコアには特別な意味はなく、これがプレースホルダーであることを視覚的に示すことができるという理由だけで、他の記号に置き換えることができます。

パイプ演算子は「await」関数でも機能します。

x |> fを待つ
// と同等
f(x)を待つ

const userAge = userId |> await fetchUserById |> getAgeFromUser;
// と同等
const userAge = getAgeFromUser(await fetchUserById(userId));

パイプライン演算子は、複数ステップのデータ処理に非常に役立ちます。

const 数値 = [10, 20, 30, 40, 50];

const処理済み数値 = 数値
  |> (_ => _.map(n => n / 2)) // [5, 10, 15, 20, 25]
  |> (_ => _.filter(n => n > 10)); // [15, 20, 25]

上記の例では、パイプライン オペレーターはデータ処理の各ステップを明確に表現し、コードの可読性を向上させることができます。

Math.signbit()

JavaScript は内部で 64 ビット浮動小数点数 (国際標準 IEEE 754) を使用して数値を表します。 IEEE 754 では、64 ビット浮動小数点数の最初のビットが符号ビットであり、「0」は正の数を表し、「1」は負の数を表すと規定しています。したがって、2 種類のゼロが存在します。「+0」は符号ビットが「0」の場合のゼロであり、「-0」は符号ビットが「1」の場合のゼロです。実際のプログラミングでは、値が「+0」であるか「-0」であるかを判断するのは、等しいため非常に面倒です。

+0 === -0 // true

ES6 の新しい Math.sign() メソッドは、値の符号を決定するためにのみ使用でき、値の符号ビットを決定するのにはあまり役に立ちません。パラメータが「-0」の場合は「-0」が返されますが、符号ビットが「1」であるか「0」であるかを直接知ることは依然として不可能であるためです。

Math.sign(-0) // -0

現在、提案書 があり、数値の符号ビットが設定されているかどうかを判断する Math.signbit() メソッドが導入されています。

Math.signbit(2) //false
Math.signbit(-2) //true
Math.signbit(0) //false
Math.signbit(-0) //true

ご覧のとおり、このメソッドは設定されている -0 の符号ビットを正しく返します。

この手法のアルゴリズムは以下の通りです。

  • パラメータが NaN の場合、false を返します
  • パラメータが -0 の場合、true を返します
  • パラメータが負の値の場合、「true」を返します。
  • それ以外の場合は false を返します

二重コロン演算子

アロー関数は「this」オブジェクトをバインドできるため、「this」オブジェクトへの明示的なバインディング (「call()」、「apply()」、「bind()」) の数が大幅に減少します。ただし、アロー関数はすべての状況に適しているわけではないため、現在は 提案 があり、「関数バインド」演算子を提案しています。これは を置き換えるために使用されます。 call()apply()bind() 呼び出し。

関数バインディング演算子は 2 つのコロン (::) が並んでおり、左側にオブジェクト、右側に関数があります。この演算子は、左側のオブジェクト (つまり、「this」オブジェクト) を右側の関数にコンテキストとして自動的にバインドします。

foo::bar;
// と同等
bar.bind(foo);

foo::bar(...引数);
// と同等
bar.apply(foo, 引数);

const hasOwnProperty = Object.prototype.hasOwnProperty;
関数 hasOwn(obj, key) {
  obj::hasOwnProperty(key) を返します。
}

二重コロンの左側が空で、右側がオブジェクトのメソッドである場合、メソッドをオブジェクトにバインドすることと同じです。

var メソッド = obj::obj.foo;
// と同等
var メソッド = ::obj.foo;

let log = ::console.log;
// と同等
var log = console.log.bind(コンソール);

二重コロン演算子の結果がまだオブジェクトである場合は、チェーン書き込みを使用できます。

「iterlib」から {map, takewhile, forEach } をインポートします。

getPlayers()
::map(x => x.character())
::takewhile(x => x.strength > 100)
::forEach(x => console.log(x));

レルム API

Realm API は、分離されたコードがグローバル オブジェクトを取得できないようにするサンドボックス機能 (サンドボックス) を提供します。

以前は、<iframe> がサンドボックスとしてよく使用されていました。

const globalOne = ウィンドウ;
let iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const globalTwo = iframe.contentWindow;

上記のコードでは、<iframe> のグローバル オブジェクトは独立しています (iframe.contentWindow)。 Realm API はこの機能を置き換えることができます。

const globalOne = ウィンドウ;
const globalTwo = new Realm().global;

上記のコードでは、「Realm API」は別のグローバル オブジェクト「new Realm().global」を提供します。

Realm API は、Realm オブジェクトを生成するための Realm() コンストラクターを提供します。オブジェクトの「global」プロパティは、元のトップレベル オブジェクトに似た新しいトップレベル オブジェクトを指します。

const globalOne = ウィンドウ;
const globalTwo = new Realm().global;

globalOne.evaluate('1 + 2') // 3
globalTwo.evaluate('1 + 2') // 3

上記のコードでは、Realm によって生成されたトップレベル オブジェクトの evaluate() メソッドによってコードを実行できます。

次のコードは、レルムのトップレベル オブジェクトと元のトップレベル オブジェクトが 2 つのオブジェクトであることを証明できます。

let a1 = globalOne.evaluate('[1,2,3]');
let a2 = globalTwo.evaluate('[1,2,3]');
a1.prototype === a2.prototype; // false
a1 インスタンスof globalTwo.Array; // false
a2 インスタンスの globalOne.Array // false;

上記のコードでは、Realm サンドボックス内の配列のプロトタイプ オブジェクトは、元の環境の配列とは異なります。

Realm サンドボックスは、ECMAScript 構文によって提供される API のみを実行でき、ホスト環境によって提供される API を実行できません。

globalTwo.evaluate('console.log(1)')
// エラーをスローします: コンソールが未定義です

上記のコードでは、Realm サンドボックスにコンソール オブジェクトがないため、エラーが発生します。 「console」は標準構文ではないため、ホスト環境によって提供されます。

この問題を解決したい場合は、次のコードを使用できます。

globalTwo.console = globalOne.console;

Realm() コンストラクターは、R​​ealm サンドボックスが元のトップレベル オブジェクトから継承するメソッドを intrinsics 属性で指定できるパラメーター オブジェクトを受け入れることができます。

const r1 = 新しいレルム();
r1.global === これ;
r1.global.JSON === JSON; // false

const r2 = new Realm({ intrinsics: 'inherit' });
r2.global === // false;
r2.global.JSON === JSON; // true

上記のコードでは、通常の状況では、サンドボックスの JSON メソッドは元の JSON オブジェクトとは異なります。ただし、Realm() コンストラクターがパラメーターとして { intrinsics: 'inherit' } を受け入れた後は、元のトップレベル オブジェクトのメソッドを継承します。

ユーザーは「Realm」のサブクラスを定義して独自のサンドボックスをカスタマイズできます。

class FakeWindow extends Realm {
  init() {
    super.init();
    グローバル = this.global; にします。

    global.document = 新しい FakeDocument(...);
    global.alert = 新しいプロキシ(fakeAlert, { ... });
    // ...
  }
}

上記のコードでは、FakeWindow が偽のトップレベル オブジェクト window をシミュレートします。

JSON モジュール

import コマンドは現在 ES モジュールのロードにのみ使用できます。JSON モジュールのロードを可能にする プロポーザル が追加されました。

JSON モジュール ファイル「config.json」があると仮定します。

{
  "appName": "私のアプリ"
}

現在、JSON モジュールは「fetch()」を使用してのみロードできます。

const 応答 = await fetch('./config.json');
const json = 応答を待ちます.json();

import コマンドで JSON モジュールを直接ロードできるようになったら、次のように記述できます。

'./config.json' から configData をインポートします。assert { type: "json" };
console.log(configData.appName);

上記の例では、JSON オブジェクト全体が configData オブジェクトとしてインポートされ、このオブジェクトから JSON データを取得できます。

「import」コマンドでJSONモジュールをインポートする場合、コマンド末尾の「assert {type: "json"}」は必須です。これはインポート アサーションと呼ばれ、JSON モジュールがロードされていることを JavaScript エンジンに伝えるために使用されます。なぜ「.json」接尾辞で判断しないのかと疑問に思われるかもしれません。ブラウザにはサフィックス名によってファイルの種類を判断しないという伝統があるため、標準委員会はこのアプローチに従うことを望んでおり、これによりセキュリティ上の問題も回避できます。

インポート アサーションは、JavaScript で他の形式のモジュールをインポートする標準的な方法であり、JSON モジュールは、この構文を使用してインポートされる最初のモジュールになります。将来的には、CSS モジュールや HTML モジュールなどのインポートもサポートされる予定です。

動的にロードされるモジュールの import() 関数は、JSON モジュールのロードもサポートしています。

import('./config.json', {assert: { type: 'json' } })

スクリプトが JSON モジュールをロードした後、export コマンドを使用して出力することもできます。現時点では、エクスポートとインポートを 1 つのステートメントに結合できます。

'./config.json' からエクスポート { config } をアサート { タイプ: 'json' };

作者: wangdoc

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

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