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 をアップグレードする方法

推薦する

CSS で左上の三角形を作成するいくつかの方法の詳細な説明

今日は、CSS を使用して左上の三角形を記述するいくつかの方法を紹介します。概略図(幅と高さを60p...

Webフォーム作成スキル

実際、上記の 3 つの表はいずれも 3 行 3 列です。区切り線を非表示にするコツはルールにあります...

時系列転位修復ケースを実装するSQL

目次1. 要件の説明2. アイデアの概要1. 延長を要求する2. アイデアの概要3. SQLコード1...

MySQL の隠し列の詳細表示

目次1. 主キーが存在する2. 主キーはないが、一意のインデックスが存在する3. 共同主キーまたは共...

Vue命令の実装原理の分析

目次1. 基本的な使い方2. 指示の動作原理2.1. 初期化2.2 テンプレートのコンパイル2.3....

vue3+vite プロジェクトで svg を使用する方法の詳細なグラフィック説明

今日、vue3+viteプロジェクトの実践で、svgを使用する場合、以前の記述方法が使用できないこと...

Pythonで書かれたWebアプリケーションをDockerでデプロイする実践

目次1. Dockerをインストールする2. コードを書く3. Dockerfileを書く4. 画像...

React-Dropzone をベースにアップロードコンポーネント機能を開発する (サンプルデモ)

今回はReact-Flaskフレームワーク上でアップロードコンポーネントを開発するスキルについてお話...

MySQL 8の新機能ウィンドウ関数の役割

MySQL 8.0 の新機能は次のとおりです。 Unicode 9.0 をすぐに完全にサポートウィン...

JavaScript でよく使われる 3 つの Web エフェクトの詳細な説明

目次1要素オフセットシリーズ1.1 オフセットの概要1.2 オフセットとスタイルの違い視覚領域クライ...

Docker で Springboot プロジェクトを実行する実装

導入: springboot プロジェクトを実行する Docker の構成は実は非常にシンプルで、L...

Win10 インストール Linux システム チュートリアル ダイアグラム

Windows システムに仮想マシンをインストールするには、 VMware Workstationソ...

MySQLのインストールと設定に関する詳細なチュートリアル

目次インストール不要のMySQLバージョン1. インストール パッケージをダウンロードします。 2....

CentOS に Nginx をインストールする方法

公式ドキュメント: https://nginx.org/en/linux_packages.html...