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

推薦する

Linux でリモート サーバー ファイルの状態を表示する方法

以下のように表示されます。 test コマンドはファイルが存在するかどうかを判断します。 ssh u...

HTML+CSS+JavaScript でシンプルな三目並べゲームを作成する

目次HTMLの実装CSSを追加Javascript部分の実装デモアドレス HTMLの実装まず、hea...

設定操作からMySQLへのNULLが見つからない問題を解決する

興味深い発見:合計 1000 件のレコードを含むテーブルがあります。クエリ ステートメントは次のよう...

CSS3 フレックスボックス自動記入の書き方を詳しく解説

この記事では、主に CSS3 フレックス エラスティック ボックスの自動塗りつぶしの書き方について詳...

MySQLのMVCCマルチバージョン同時実行制御の実装

1 MVCCとは何かMVCC の正式名称は、マルチバージョン同時実行制御です。データベースへの同時ア...

Reactでレシピシステムを実装する方法を解説した記事

目次1. レシピ集1.1 プロジェクトの背景1.2 テクノロジースタック1.3 開発環境1.4. プ...

JavaScript 状態コンテナ Redux の詳細な説明

目次1. Reduxを選ぶ理由2. Reduxデータフロー3つの原則4. Reduxソースコード分析...

Linux で MySQL データベースのデータ ファイル パスを変更する手順

rpm インストール方法を使用して MySQL データベースをインストールした後、データ ファイルの...

JS配列の組み込みトラバーサルメソッドとその違いについての簡単な説明

目次forEach() (ES6) メソッドmap() (ES6) メソッドflatMap() メソ...

Nginx シグナル制御

Nginx の紹介Nginx は、高性能な HTTP およびリバース プロキシ サーバーであり、IM...

Linux コマンドラインからファイルを削除する実用的な方法

rm コマンドrm コマンドは、ファイルを削除するときによく使用されるコマンドです。ファイルまたはデ...

WeChatアプレットがテキストスクロールを実装

この記事の例では、WeChatアプレットでテキストスクロールを実装するための具体的なコードを参考まで...

Vue2.x および Vue3.x のカスタム命令の使用方法とフック関数の原理を理解する

目次Vue2.x の使用法グローバル登録部分登録使用フック機能フック関数のパラメータVue3.x の...

vue+tsは要素のマウスドラッグの効果を実現します

この記事の例では、要素のマウスドラッグ効果を実現するためのvue+tsの具体的なコードを参考までに共...

Dockerコンテナでは、イメージを簡素化してサイズを縮小する方法を詳しく説明しています

目次1.画像レイヤーの数を減らす1. 命令の統合2. 多段階ビルド3. スクワッシュ機能を有効にする...