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 を扱ったことがある人なら、テーブル メタデータ ロックの待機についてよく知っているは...

Linux ドメイン ネーム サービス DNS 設定方法

DNSとはDNS の正式名称は Domain Name System で、ドメイン名解決システムを意...

ChromeはCookieの変更を監視し、値を割り当てます

次のコードは、Chrome による Cookie の変更の監視を導入しています。コードは次のとおりで...

MySQLでのカスタムパラメータの使用に関する詳細な説明

MySQL 変数には、システム変数とシステム変数が含まれます。今回の学習課題はユーザー定義変数です。...

IDEA2020.1.2 Webプロジェクトの作成とTomcatの設定に関する詳細なチュートリアル

この記事は、IDEA で Web プロジェクトを作成し、Tomcat を構成する方法についての統合記...

VMware 仮想マシンのネットワークの問題の解決方法

目次1. 問題の説明2. 問題解決1. 仮想マシンシステムのインストール時にネットワークがない場合2...

UbuntuにMySQLをインストールするときにデフォルトのパスワードを変更する詳細な手順

ステップ1: ディレクトリに入ります: cd /etc/mysql、debian.cnfファイルを表...

JS の toFixed() メソッドの丸め精度の問題の詳細な説明

目次落とし穴充填方法何の穴ですか?要約する落とし穴最近、仕事で商品の割引価格を計算すると、いつも1セ...

mysql5.7 ユーザー権限の作成、ユーザーの削除、権限の取り消し

1. ユーザーを作成します。注文: 'password' によって識別される ...

js配列のfind、some、filter、reduceの違いの詳細な説明

Array の filter、find、some、reduce メソッドの違いを区別し、使用シナリオ...

Vueは小さな検索機能を実装する

この記事の例では、検索機能を実装するためのVueの具体的なコードを参考までに共有しています。具体的な...

threejs でリアルタイムポリゴン屈折を実装する方法

目次序文ステップ1: セットアップと前方屈折ステップ2: 反射とフレネル方程式ステップ3: 多面屈折...

VMware仮想マシンを使用してUbuntu 20.04をインストールする完全なチュートリアル

Ubuntu は比較的人気のある Linux デスクトップ システムです。最近、Ubuntu 20....

CSS3で実装された3Dトンネル効果

達成された効果実装コードhtml <div class="scene"&g...

CSS 等高レイアウトの一般的な方法

等高レイアウト同じ親コンテナー内の同じ高さの子要素のレイアウトを指します。等高レイアウトの実装の観点...