JS関数のカリー化の詳細な説明

JS関数のカリー化の詳細な説明

1. 補足知識ポイント: 関数の暗黙的な変換

ここで考えるべき簡単な質問があります。

関数fn() {
    20を返します。
}
console.log(fn + 10); // 出力は何ですか?

少し修正して、出力がどうなるか考えてみましょう。

関数fn() {
    20を返します。
}
 
fn.toString = 関数() {
    10 を返します。
}
 
console.log(fn + 10); // 出力は何ですか?

引き続き修正することができます。

関数fn() {
    20を返します。
}
 
fn.toString = 関数() {
    10 を返します。
}
 
fn.valueOf = 関数() {
    5を返します。
}
 
console.log(fn + 10); // 出力は何ですか?
// 出力結果は function fn() {
    20を返します。
}
10
 
20
 
15

console.log を使用する場合、または計算を実行する場合、暗黙的な変換が発生する可能性があります。上記の 3 つの例から、関数の暗黙的な変換についていくつかの結論を導き出すことができます。

toString と valueOf を再定義しない場合、関数の暗黙的な変換によってデフォルトの toString メソッドが呼び出され、関数の定義が文字列として返されます。 toString/vauleOf メソッドを積極的に定義すると、暗黙的な変換の戻り結果は自分で制御されます。その中で、valueOf は toString よりも優先度が高くなります。

したがって、上記の例の結論は理解しやすいです。ぜひ試してみることをお勧めします。

2. 補足知識: call/apply を使って配列を囲む map メソッド

map(): 配列内の各項目に対して指定された関数を実行し、各関数呼び出しの結果の配列を返します。

簡単に言えば、配列の各要素を走査し、計算を実行した後、計算結果を map (コールバック関数) の最初のパラメータで返すことを意味します。すべての計算結果からなる新しい配列を返します。

// コールバック関数には 3 つのパラメータがあります // 最初のパラメータは newArr 内の各項目を表し、2 番目のパラメータは配列内の項目のインデックス値を表します // 3 番目のパラメータは配列自体を表します // さらに、コールバック関数内の this では、2 番目のパラメータが map に存在しない場合は失われたオブジェクトを指し、2 番目のパラメータが存在する場合はパラメータによって設定されたオブジェクトを指します var newArr = [1, 2, 3, 4].map(function(item, i, arr) {
    console.log(item, i, arr, this); // 試してみる return item + 1; // 各項目に 1 を加算する
}, { a: 1 })
 
console.log(newArr); // [2, 3, 4, 5]

map メソッドの詳細については、上記の例のコメントで説明されています。今、私たちはマップをどのようにカプセル化するかという難しい問題に直面しています。

まず for ループについて考えてみましょう。 for ループを使用してマップを実装できますが、カプセル化するときにいくつかの問題を考慮する必要があります。 for ループを使用する場合、ループ処理をカプセル化するのは確かに簡単ですが、 for ループ内の各項目に対して実行する必要があることを固定されたものでカプセル化するのは困難です。それぞれのシナリオでは、for ループ内のデータの処理が異なる必要があるためです。

そこで、これらの異なる操作を別の関数で処理する良い方法を考え、この関数を map メソッドの最初のパラメーターにしました。このコールバック関数内の特定の操作は、使用時に自分で決定します。したがって、この考えに基づいたカプセル化の実装は次のようになります。

Array.prototype._map = function(fn, context) {
    var temp = [];
    if(typeof fn == '関数') {
        var k = 0;
        var len = this.length;
        // forループ処理をカプセル化 for(; k < len; k++) {
            // 各操作を fn に投入し、call メソッドを使用して fn の this ポインタと特定のパラメータを指定します temp.push(fn.call(context, this[k], k, this))
        }
    } それ以外 {
        console.error('TypeError: '+ fn +' は関数ではありません。');
    }
 
    // 各操作の結果で構成される新しい配列を返します return temp;
}
 
var newArr = [1, 2, 3, 4]._map(関数(項目) {
    アイテム + 1 を返します。
})
// [2, 3, 4, 5]

上記のパッケージでは、最初に最終的な戻り結果を格納するために使用される空の temp 配列を定義しました。 for ループでは、ループが実行されるたびにパラメーター fn 関数が 1 回実行され、fn のパラメーターは call メソッドを使用して渡されます。

map のカプセル化プロセスを理解すると、map を使用するときに最初のコールバック関数に常に戻り値があることが期待される理由がわかります。 eslint のルールでは、map を使用するときに戻り値を設定しないとエラーと判断されます。

さて、関数の暗黙的な変換ルールと、このシナリオでの call/apply の使用方法を理解したので、簡単な例を通してカリー化を理解してみることにします。

3. 浅いところから深いところまでカレーを作る

フロントエンドの面接では、カリー化に関する質問が広く出回っています。

計算結果が次の期待を満たすように add メソッドを実装します。

(1)(2)(3)を加えると6になる
(1, 2, 3)(4)を足すと10になる
(1)(2)(3)(4)(5)を加えると15になる

当然、計算結果はすべてのパラメータの合計です。add メソッドが実行されるたびに、同じ関数を返して残りのパラメータの計算を続ける必要があります。

最も単純な例から始めて、段階的に解決策を探すことができます。

2 回だけ呼び出す場合は、次のようにカプセル化できます。

関数 add(a) {
    関数(b)を返す{
        a + b を返します。
    }
}
 
コンソール.log(add(1)(2)); // 3

3 回だけ呼び出す場合:

関数 add(a) {
    関数(b)を返す{
        関数を返す (c) {
            a + b + c を返します。
        }
    }
}
 
コンソール.log(add(1)(2)(3)); // 6

上記のカプセル化は、私たちが望む結果に少し似ていますが、パラメータの使用が非常に制限されているため、私たちが望む最終結果ではありません。一般的なカプセル化が必要です。どうすればいいですか?上記の 2 つの例をまとめると、実際にはクロージャの特性を利用して、最後に返される関数にすべてのパラメータを集中させ、計算して結果を返します。したがって、カプセル化する場合の主な目的は、パラメータを一緒に計算することです。

具体的な実装を見てみましょう。

関数add(){
    // 初めて実行するときに、すべてのパラメータを格納する配列を定義します var _args = [].slice.call(arguments);
 
    // 内部で関数を宣言し、クロージャ機能を使用して_argsを保存し、すべてのパラメータ値を収集します。var adder = function() {
        var _adder = 関数() {
            [].push.apply(_args、[].slice.call(引数));
            _adder を返します。
        };
 
        // 暗黙的な変換機能を使用すると、関数が最終的に実行されるときに暗黙的な変換が実行され、最終的な値が計算されて返されます _adder.toString = function () {
            _args.reduce(関数(a, b) {を返す
                a + b を返します。
            });
        }
 
        _adder を返します。
    }
    adder.apply(null, [].slice.call(arguments)) を返します。
}
 
// 結果を出力します。パラメータは自由に組み合わせることができます。 console.log(add(1, 2, 3, 4, 5)); // 15
コンソール.log(add(1, 2, 3, 4)(5)); // 15
コンソール.log(add(1)(2)(3)(4)(5)); // 15

上記の実装では、クロージャの特性を利用しています。主な目的は、いくつかの巧妙な方法を通じて配列内のすべてのパラメータを収集し、最終的な暗黙的な変換中に配列内のすべての項目を合計することです。したがって、add メソッドを呼び出す場合、パラメーターは非常に柔軟になります。もちろん、私たちのニーズも簡単に満たしてくれました。

上記のデモを理解したところで、カリー化の定義を見てみましょう。理解しやすくなると思います。

カリー化は部分評価とも呼ばれ、複数のパラメータを受け入れる関数を、単一のパラメータ (元の関数の最初のパラメータ) を受け入れ、残りのパラメータを受け入れて演算の結果を返す新しい関数に変換する手法です。

  • 単一のパラメータを受け取る場合は、多くの情報を伝達する必要があるため、コールバック関数がよく使用されます。
  • コールバック関数などを通じて、いくつかのパラメータを関数に渡します。
  • 渡すすべてのパラメータを処理する新しい関数を返します。

上記の例では、add(1, 2, 3, 4)をadd(1)(2)(3)(4)に変換できます。これは部分的な評価です。毎回渡されるパラメータは、渡したいすべてのパラメータの一部のみです。もちろん、実際のアプリケーションでは、パラメータがそれほど複雑に処理されることは少なく、単純に 2 つの部分に分割される場合が多くあります。

カレー作りに関するもう一つの疑問について考えてみましょう。

配列内の各項目を必要な文字に接続する必要がある計算要件があるとします。私たちは何をすべきでしょうか? join メソッドを使用することを考えるのは非常に簡単です。

var arr = [1, 2, 3, 4, 5];
 
// 実際の開発では、新しいメソッドでArrayを直接拡張することは推奨されません // これは単にそれをより明確に示すための方法です Array.prototype.merge = function(chars) {
    this.join(chars) を返します。
}
 
var 文字列 = arr.merge('-')
 
console.log(文字列); // 1-2-3-4-5

難易度を上げるには、各項目に数字を追加して、それらを接続します。次に、各項目に対して特別な操作を実行し、新しい配列を生成して、それらを文字で接続するために、ここでマップが必要になります。実装は次のとおりです。

var arr = [1, 2, 3, 4, 5];
 
Array.prototype.merge = function(chars, number) {
    this.map(function(item) { を返す
        アイテム + 番号を返します。
    }).join(文字);
}
 
var 文字列 = arr.merge('-', 1);
 
console.log(文字列); // 2-3-4-5-6

しかし、配列内の各項目から配列を減算し、それらを連結したい場合はどうすればよいでしょうか?もちろん、実装は上記の加算演算と同じです。

var arr = [1, 2, 3, 4, 5];
 
Array.prototype.merge = function(chars, number) {
    this.map(function(item) { を返す
        アイテム番号を返します。
    }).join(文字);
}
 
var 文字列 = arr.merge('~', 1);
 
console.log(文字列); // 0~1~2~3~4

賢い友人たちはその混乱に気づいたに違いない。異なる計算プロセスを同時に処理できる関数をカプセル化したいと考えていますが、各操作をカプセル化するために固定ルーチンを使用することはできません。したがって、問題は、マップをカプセル化するときに直面する問題と同じになります。カリー化の助けを借りてこれを実現できます。

マップのカプセル化と同じ原理で、各データをどのように処理するかは事前にわからないため、処理してから文字で接続する必要があることだけがわかっているので、処理内容を関数に保存することもできます。そしてパッケージを固定して接続する部分のみが必要となります。

したがって、次のパッケージがあります。

// カプセル化は非常に簡単で、1 つの文で完了します Array.prototype.merge = function(fn, chars) {
    this.map(fn).join(chars) を返します。
}
 
var arr = [1, 2, 3, 4];
 
// 難しいのは、実際に使用する際の操作をどのように定義するか、そして渡された num パラメータをクロージャを使用して保存するかです。var add = function(num) {
    関数(項目)を返す{
        アイテム + 数値を返します。
    }
}
 
var red = 関数(数値) {
    関数(項目)を返す{
        アイテム番号を返します。
    }
}
 
// 各項目に 2 を加算してマージします var res1 = arr.merge(add(2), '-');
 
// 各項目から2を減算して結合します。var res2 = arr.merge(red(1), '-');
 
// コールバック関数を直接使用して、各項目を2倍にしてマージすることもできます。var res3 = arr.merge((function(num) {
    関数(項目)を返す{
        アイテム * 番号を返す
    }
})(2)、'-')
 
コンソール.log(res1); // 3-4-5-6
コンソールログ(res2); // 0-1-2-3
コンソールログ(res3); // 2-4-6-8

上記の例からカレーの特徴が分かりますか?

4. カリー化の一般化

一般的なカリー化メソッドは、実際には上でカプセル化した add メソッドよりもはるかに単純です。

var カリー化 = function(fn) {
    var args = [].slice.call(引数, 1);
 
    関数()を返す{
        // 主な目的は、統一された計算のためにすべての必要なパラメータを配列に収集することです var _args = args.concat([].slice.call(arguments));
        fn.apply(null, _args) を返します。
    }
}
 
var sum = カリー化(function() {
    var args = [].slice.call(引数);
    args.reduce(function(a, b) { を返す
        a + b を返します。
    })
}, 10)
 
コンソール.log(合計(20, 10)); // 40
コンソール.log(合計(10, 5)); // 25

5. カリー化とバインド

Object.prototype.bind = function(context) {
    var _this = これ;
    var args = [].prototype.slice.call(引数, 1);
 
    関数()を返す{
        _this.apply(コンテキスト、引数) を返す
    }
}

この例では、call と apply を柔軟に使用して bind 関数を実装します。

これまでの例では、カリー化の特徴を次のようにまとめることができます。

  • 1 つのパラメータを受け取り、コールバック関数を介してさらにパラメータを渡しますか?
  • 渡したいすべてのパラメータを処理する新しい関数を返します。
  • パラメータを収集するには、call/apply および arguments オブジェクトを使用する必要があります。
  • 返された関数は、収集されたパラメータを処理するために使用されます。

これを読んで、皆さんがカリー化の概念を大まかに理解してもらえれば幸いです。カリー化を上手に使いたいなら、もっと実践的な経験が必要です。

上記は、JS 関数のカリー化の詳細についての詳細な説明です。JS 関数のカリー化の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • JavaScript 関数のカリー化
  • JavaScript 関数のカリー化の簡単な分析
  • js関数カリー化の使用方法と例の分析
  • JavaScript 関数のカリー化の説明
  • JavaScript 関数のカリー化の説明

<<:  Easyswoole ワンクリック インストール スクリプトとパゴダ インストール エラー

>>:  MySQL マスタースレーブレプリケーションスレッドの状態遷移に関する詳細な理解

ブログ    

推薦する

MySQL のクラスター化インデックスとクラスター化インデックスの成長の仕組みを理解する

このノートでは、 MySQL の B+Tree インデックスとは何ですか?クラスター化インデックスは...

Node.js のイベント モジュールに関する知識ポイントのまとめ

Node の研究と応用を通じて、NodeJS はシングルスレッド、イベント駆動型、非ブロッキング I...

Alibaba Cloud Server への Web プロジェクトのデプロイについて (5 つの手順)

1.まずAlibaba Cloudのウェブサイトにログインしてアカウントを登録し、サーバータイプを...

レスポンシブデザインについての簡単な説明

1. レスポンシブ デザインとは何ですか?レスポンシブデザインとは、ウェブサイトの開発プロセス中に、...

ネイティブ CSS で無限テキストカルーセルを実装する一般的な方法

テキストカルーセルは私たちの日常生活で非常に一般的です。スーパーマーケットや実店舗の入り口には、テキ...

JS でシンプルなデータ監視を実装する方法

目次概要最初のステップステップ2なぜ別の _data が必要なのでしょうか?データにもう少しデータを...

Vue ElementUI フォームのフォーム検証

フォーム検証は、フロントエンド開発プロセスで最もよく使用される機能の 1 つです。私の個人的な仕事経...

JavaScript におけるさまざまなバイナリオブジェクトの関係の詳細な説明

目次序文さまざまなオブジェクト間の関係配列バッファ型付き配列Uint8ClampedArray文字間...

USE DB 輻輳に対する MySQL ソリューションの詳細な説明

障害に遭遇すると、障害の根本的な原因を考えるのではなく、障害を解決する方法を考えることがよくあります...

Vue: メモリリークの詳細な説明

メモリリークとは何ですか?メモリ リークとは、新しいメモリが作成されたが、解放またはガベージ コレ...

MySQLトランザクションを実行するための構文とプロセスの詳細な説明

概要: MySQL は、トランザクションをサポートするためにさまざまなストレージ エンジンを提供しま...

MySQL で期限切れのデータレコードを定期的に削除する簡単な方法

1. MySQL に接続してログインしたら、まず MySQL でイベント機能が有効になっているかどう...

javascript:void(0) の意味と使用例

voidキーワードの紹介まず、void キーワードは JavaScript で非常に重要なキーワード...

JavaScript デザインパターン 責任連鎖パターン

目次概要コードの実装パラメータ定義成し遂げる責任連鎖パターンの実装改善概要責任チェーン パターンは、...

MySQLにおけるトランザクション分離レベルの実装原理の詳細な説明

序文データベース トランザクションに関して言えば、トランザクションの ACID 特性、分離レベル、解...