ES5とES6の違いを分析する

ES5とES6の違いを分析する

概要

ご存知のとおり、ES6 では、グローバルで組み込みの非構築可能な Reflect オブジェクトが追加され、一連のインターセプト可能な操作メソッドが提供されます。その 1 つが Reflect.apply() です。従来の ES5 Function.prototype.apply() との類似点と相違点を調べてみましょう。

関数シグネチャ

MDN 上の 2 つの関数シグネチャは次のとおりです。

Reflect.apply(ターゲット、thisArgument、argumentsList)
function.apply(thisArg, [argsArray])

TypeScript で定義されている関数シグネチャは次のとおりです。

名前空間Reflectを宣言する{
    関数を適用します(ターゲット: Function、thisArgument: any、argumentsList: ArrayLike<any>): any;
}
インターフェース関数{
    適用(this: Function、thisArg: any、argArray?: any): any;
}

これらはすべて、呼び出された関数に提供される this パラメータとパラメータの配列 (または配列のようなオブジェクト) を受け入れます。

オプションパラメータ

最も直感的にわかるのは、function.apply() によって関数に渡される 2 番目のパラメータ「パラメータ配列」がオプションであることです。呼び出された関数にパラメータを渡す必要がない場合は、パラメータを渡さなかったり、null 値や未定義値を渡したりすることはできません。 function.apply() には 2 つのパラメータしかないため、実際には最初のパラメータも省略でき、原理的には実装で未定義の値を取得できます。

(関数() { console.log('test1') }).apply()
//テスト1
(関数 () { console.log('test2') }).apply(未定義、[])
//テスト2
(関数 () { console.log('test3') }).apply(未定義、{})
//テスト3
(関数 (テキスト) { console.log(テキスト) }).apply(未定義、['test4'])
//テスト4

Reflect.apply() では、すべてのパラメータを渡す必要があります。呼び出された関数にパラメータを渡したくない場合は、空の配列または空の配列のようなオブジェクトを入力する必要があります (純粋な JavaScript では空のオブジェクトも許容されますが、TypeScript の場合は、型チェックに合格するために長さが 0 のキーと値のペアを使用する必要があります)。

Reflect.apply(関数() { console.log('test1') }, 未定義)
// スロー:
// TypeError: CreateListFromArrayLike が非オブジェクトに対して呼び出されました
Reflect.apply(関数() { console.log('test2') }, 未定義、[])
//テスト2
Reflect.apply(関数() { console.log('test3') }, 未定義, {})
//テスト3
Reflect.apply(関数(テキスト) { console.log(テキスト) }, 未定義、['test4'])
//テスト4

非厳密モード

ドキュメントによると、function.apply() の thisArg パラメータは非厳密モードでは異なります。その値が null または undefined の場合、グローバル オブジェクト (ブラウザーのウィンドウ) に自動的に置き換えられ、基本データ型の値が自動的にラップされます (たとえば、リテラル 1 のラップされた値は Number(1) に相当します)。

(関数() { console.log(this) }).apply(null)
// ウィンドウ {...}
(関数() { console.log(this) }).apply(1)
// 数値 { [[PrimitiveValue]]: 1 }
(関数() { console.log(this) }).apply(true)
// ブール値 { [[PrimitiveValue]]: true }
'厳密な使用';
(関数() { console.log(this) }).apply(null)
// ヌル
(関数() { console.log(this) }).apply(1)
// 1
(関数() { console.log(this) }).apply(true)
// 真実

しかし、テストの結果、非厳密モードでの上記の動作は Reflect.apply() でも有効であることがわかりましたが、MDN ドキュメントにはこれについて記載されていません。

例外処理

Reflect.apply は Function.prototype.apply のカプセル化とみなすことができ、一部の例外判定は同じです。渡されたターゲット関数ターゲットが実際には呼び出し可能ではない、関数ではないなどの場合は、例外がトリガーされます。しかし、異常な症状は異なる場合があります。

ターゲット パラメータに関数ではなくオブジェクトを渡すと、例外が発生します。

Function.prototype.apply() によってスローされる例外のセマンティクスは不明瞭です。文字通りの翻訳は、.call は関数ではないということです。ただし、正しい呼び出し可能な関数オブジェクトを渡すと、エラーは報告されません。これにより、Function.prototype.apply の下に call 属性があるかどうかについて混乱が生じます。

関数プロトタイプを適用します。呼び出し()
// スロー:
// TypeError: Function.prototype.apply.call は関数ではありません
Function.prototype.apply.call(コンソール)
// スロー:
// TypeError: Function.prototype.apply.call は関数ではありません
関数プロトタイプを適用して呼び出します(console.log)
///- 予想通り、出力は空です

Function.prototype.apply() によってスローされる例外はあいまいです。また、ターゲット パラメータに呼び出し不可能なオブジェクトが渡されます。2 番目と 3 番目のパラメータが入力されている場合、スローされる例外の説明は上記とはまったく異なります。

ただし、呼び出し不可能なオブジェクトのみを渡す Reflect.apply() の例外は、すべてのパラメータを指定した Function.prototype.apply() の例外と同じです。

Reflect.apply(コンソール)
// スロー:
// TypeError: Function.prototype.apply が #<Object> に対して呼び出されましたが、これはオブジェクトであり関数ではありません

正しい呼び出し可能な関数が渡された場合、3 番目のパラメータ配列のパラメータがチェックされます。これは、Reflect.apply() のパラメータ チェックが順次行われることも示しています。

反映.適用(コンソール.ログ)
// スロー:
// TypeError: CreateListFromArrayLike が非オブジェクトに対して呼び出されました

実用

プロキシ以外での使用例はまだ多くありませんが、互換性の問題が徐々に軽減されれば、使用率は徐々に増加すると考えています。

ES6Reflect.apply() の形式は、従来の ES5 の使用法よりも直感的で読みやすく、コード行でどの関数を使用するかがわかりやすくなり、期待される動作を実行できるようになります。

// ES5
Function.prototype.apply.call(<Function>, undefined, [...])
<関数>.apply(undefined, [...])
// ES6
Reflect.apply(<関数>, undefined, [...])

比較のために、よく使われる Object.prototype.toString を選択してみましょう。

Object.prototype.toString.apply(/ /)
// '[オブジェクト正規表現]'
Reflect.apply(Object.prototype.toString, //, [])
// '[オブジェクト正規表現]'

反対する人もいるかもしれませんが、これは書くのが長くて面倒ではないでしょうか?この点については意見が分かれています。単一の関数を繰り返し呼び出すと、より多くのコードが必要になります。柔軟な使用が必要なシナリオでは、関数型スタイルの方が適しています。関数オブジェクトを指定してパラメータを渡すだけで、期待どおりの結果が得られます。

しかし、この場合、小さな問題が発生する可能性があります。呼び出しごとに新しい空の配列を作成する必要があるのです。現在、ほとんどのデバイスのパフォーマンスは十分に優れているため、プログラマーはこの損失を考慮する必要はありませんが、高パフォーマンスで最適化されていないエンジンのシナリオでは、最初に再利用可能な空の配列を作成する方がよい場合があります。

定数空の引数 = []

関数 getType(obj) {
    Reflect.apply() を返す
        オブジェクト.prototype.toString、
        オブジェクト、
        空の引数
    )
}

String.fromCharCode() が呼び出される別のシナリオでは、コード内の文字列を混乱させる可能性があります。

反映.適用(
    文字列.fromCharCode、
    未定義、
    [104、101、108、108、
     111、32、119、111、
     [114、108、100、33]
)
// 'こんにちは世界!'

これは、Math.max() などの複数のパラメータを渡すことができる関数の場合に、より便利な場合があります。

定数arr = [1, 1, 2, 3, 5, 8]
Reflect.apply(Math.max, 未定義, arr) を適用します。
// 8
Function.prototype.apply.call(Math.max, 未定義, arr) 関数の呼び出し
// 8
Math.max.apply(未定義、arr)
// 8

ただし、言語標準ではパラメータの最大数は指定されていないため、大きすぎる配列が渡されると、スタック サイズが制限を超えたというエラーが報告される可能性があります。このサイズはプラットフォームやエンジンによって異なります。たとえば、PC 上の node.js は非常に大きなサイズに達する可能性がありますが、携帯電話上の jsC は 65536 に制限される場合があります。

const arr = 新しい配列(Math.floor(2**18)).fill(0)
// [
// 0, 0, 0, 0,
// ... 262140 件以上
// ]
Reflect.apply(Math.max, null, arr)
// スロー:
// RangeError: 最大コールスタックサイズを超えました

要約する

新しい ES6 標準で提供される Reflect.apply() は、より規則的で使いやすくなっています。次の機能があります。

1. 直感的で読みやすく、呼び出される関数はパラメータ内に配置され、関数型スタイルに近いです。

2. 例外処理は一貫しており、明確である。

3. すべてのパラメータを渡す必要があり、コンパイル時のエラーチェックと型推論がより使いやすくなります。

現在、vue.js 3 もレスポンシブ システムで Proxy と Reflect を多用しています。近い将来、フロントエンドの世界で Reflect が活躍することを期待しています。

上記は、ES5とES6のapplyの違いの詳細な分析です。ES5とES6の違いの詳細については、123WORDPRESS.COMの他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • ネイティブ js で呼び出し、適用、バインドを実装する方法
  • JavaScript の call、apply、bind の違いの詳細な説明
  • Javascript 呼び出しと適用のアプリケーション シナリオと例
  • JS call() および apply() メソッドの使用例のまとめ
  • JS 適用の使用状況の概要と使用シナリオ例の分析
  • JavaScript における this/call/apply/bind の使い方と違い
  • JavaScript 関数の呼び出し、原則例の分析の適用
  • JavaScript での call、apply、callee、caller の使用例の分析
  • JS の call() と apply() の機能と使用例の分析
  • JS での apply() の適用例の分析

<<:  Mysql5.7.17 winx64.zip 解凍バージョンのインストールと設定のグラフィックチュートリアル

>>:  Nginx で https をアップグレードする方法

推薦する

MySQL DML ステートメントの概要

DML 操作とは、データベース内のテーブル レコードに対する操作を指し、主にテーブル レコードの挿入...

UbuntuにCMakeをインストールするいくつかの方法の詳細な説明

CMakeをインストール sudo apt をインストール cmake この方法はインストールが簡単...

div 内の img と span の垂直方向の中央揃えの問題について

以下のように表示されます。 XML/HTML コードコンテンツをクリップボードにコピー<htm...

Vueウォッチの監視方法の概要

目次1. Vueにおけるwatchの役割はその名の通り、監視の役割です。 2. このオブジェクトのプ...

ubuntu20.04 LTS システムのデフォルト ソース ソース リスト ファイルの変更

誤って source.list の内容を変更し、一連のエラーが発生した場合は、デフォルトのソース フ...

初めてDockerイメージを構築、実行、公開、取得するための詳細な手順

1. はじめに以前は、Python アプリケーションの作成を開始したい場合、最初のステップはマシンに...

JSデータ型検出のさまざまな方法の概要

目次背景データ型を決定する方法は何ですか? 1. typeof を使用して基本データ型を決定します。...

Vueのprovideとinjectの使い方と原則を分析する

まず、provide/inject を使用する理由について説明しましょう。祖父コンポーネントと孫コン...

Node.js における path.join() の利点の分析

文字列連結ではなく path.join() メソッドを使用する必要があるのはなぜか疑問に思うかもしれ...

Win10 VM 仮想マシンに Mac OS10.14 を完璧にインストールする (グラフィック チュートリアル)

最近、Apple の記者会見を見てとても興奮したので、Mac システムを体験して Apple の素晴...

Dockerイメージの読み込み原理

目次Docker イメージ鏡とは何ですか? Dockerイメージの読み込み原理コミットミラーDock...

CocosCreator 学習モジュールスクリプト

Cocos Creator モジュラースクリプトCocos Creator を使用すると、コードを複...

レンダリング関数と JSX の詳細

目次1. 基本2. ノード、ツリー、仮想DOM 1. 仮想DOM 3. createElementパ...

Vue で配列をクリアするいくつかの方法 (要約)

目次1. はじめに2. データを消去するいくつかの方法2.1 ref() の使用2.2 スライスの使...

MySQL の null と not null、null と空の値の違いの詳細な説明 ''''

MySQL を長い間使用してきた多くの人は、これら 2 つのフィールド属性の概念をまだよく理解して...