Javascript で関数のカリー化とデカリー化を実装する方法

Javascript で関数のカリー化とデカリー化を実装する方法

関数のカリー化(黒い疑問符の顔)? ? ?カレー(黒い疑問符の顔)? ? ?これは完璧な中国語翻訳です。関数カリー化とは何かを見てみましょう。

Wikipedia では次のように説明されています: 複数のパラメータを受け入れる関数を、単一のパラメータ (元の関数の最初のパラメータ) を受け入れ、残りのパラメータを受け入れて結果を返す新しい関数に変換する手法。これは数学者ハスケル・ブルックス・カリーによって提案され、カリーにちなんで名付けられました。

概念はしばしば無味乾燥で理解しにくいものです。人間の言葉で説明しましょう。この関数にいくつのパラメータがあるかわからない場合は、最初にパラメータを渡し、次に JS クロージャ (JS クロージャがわからない場合は、このブログ投稿 https://www.cnblogs.com/dengyao-blogs/p/11475575.html を学ぶ前にクロージャの知識ポイントを学んでください) を使用して関数を返します。内部関数は、操作と出力の最初のパラメータを除く残りのパラメータを受け取ります。これが関数のカリー化です。

ここに小さな例を示します。

シナリオ(要件):

周知のとおり、プログラマーは毎日多くの残業をしています。プログラマーの毎日の残業時間を計算する必要がある場合、私たちの最初の反応は次のようになります。

var 残業時間 = 0;
関数time(x){
    残業+=xを返します。
}

時間(1); //1
時間(2); //3
時間(3); //6

上記のコードに問題はありませんが、呼び出されるたびに時刻を追加するのは非常に面倒であり、関数が呼び出されるたびに特定の操作を実行する必要があります。データ量が膨大な場合は、パフォーマンスに影響を与えるリスクがあります。では、問題を解決する簡単な方法はありますか?いくつかの!

関数time(x){
  関数(y)を返す{
        x+y を返します。
    }      
}

var times = time(0);
回(3);

しかし、上記のコードにはまだ問題があります。実際の開発では、パラメータが不確定な場合がよくあります。上記のコードはカリー化の基本的な操作を実装しているだけですが、パラメータが不確定な状況には対応できません。そのため、関数パラメータには制限があります。しかし、上記のコードから、関数カリー化が基本的に何を意味するのかはわかります。つまり、関数が呼び出されると、1つのパラメータのみを渡すことが許可され、その後、クロージャを介して内部関数が返され、残りのパラメータを処理して受け取ります。返された関数は、クロージャを介して最初のパラメータを記憶します。

もう一度コードを変換してみましょう。

// まず関数を受け取る変数を定義します var overtime = (function() {
//パラメータを受け取る配列を定義します var args = [];
//ここではクロージャを使用して外部関数を呼び出して内部関数を返します return function() {
  //arguments はブラウザに組み込まれたオブジェクトで、特にパラメータを受け取るために使用されます //パラメータの長さが 0 の場合、つまりパラメータがない場合 if (arguments.length === 0) {
    //累積用の変数を定義します。var time = 0;
    //ループ累積、i を args の長さと比較 for (var i = 0, l = args.length; i < l; i++) {
    //累積演算はtime=time+args[i]と同等です
        時間 += args[i];
      }
    // 累積された結果を返します return time;
    //引数オブジェクトパラメータの長さがゼロでない場合、つまりパラメータがある場合}else {
    // 定義された空の配列に引数を配列項目として追加します。最初のパラメーター args は、this ポインターを変更するために使用されます。2 番目のパラメーター arguments は、残りのパラメーターを配列として空の配列に追加します [].push.apply(args, arguments);
    }
  }
})();

残業(3.5); // 1日目 残業(4.5); // 2日目 残業(2.1); // 3日目//...

console.log( オーバータイム() ); // 10.1

コードは機能を実現するために変更されていますが、これは関数カリー化の完全な実装ではありません。では、これを完全に実現するにはどうすればよいでしょうか?ここでは一般的な実装方法を紹介します。

//カリー化メソッドを定義し、最初にパラメータを渡します var currying = function (fn) {
  //引数オブジェクトの残りのパラメータを組み立てるために空の配列を定義します。var args=[];
  //クロージャを使用して残りのパラメータを処理する関数を返します return function () {
    //引数の長さが0の場合、残りのパラメータはありません if(arguments.length===0){
    //上記のメソッドを実行します //ここで、this は s() と同様に、以下の s を指します。つまり、パラメータの長さが 0 の場合、関数が直接呼び出されます return fn.apply(this,args)
    }
    console.log(引数)
  //引数の長さが0でない場合、残りのパラメータがあります //配列のプロトタイプオブジェクトに配列を追加し、applyを使用してthisのポインタをargsに変更します
  // [].slice.call(arguments) の配列をプロトタイプ配列に追加します //ここで [].slice.call(arguments) === Array.prototype.slice.call(arguments) は基本的に、arguments オブジェクトをスライス機能を持つ配列に変換します Array.prototype.push.apply(args,[].slice.call(arguments))
    //args.push([].slice.call(引数))
    console.log(引数)
  //ここで返されるarguments.calleeは返されるクロージャ関数です。Calleeはargumentsオブジェクトのプロパティであり、実行されている関数オブジェクトを返すために使用されます。return arguments.callee
  }
}
  //ここでカリー化メソッドが呼び出され、add 関数が渡されます。結果はクロージャ内の関数を返します var s = currying(add);
  // クロージャ内で関数を呼び出します。パラメータがある場合、パラメータは args 配列に徐々に追加されます。渡されるパラメータがない場合は、直接呼び出されます。// 呼び出しはチェーン操作をサポートします s(1)(2)(3)();
// 一度に複数のパラメータ s(1,2,3) を渡すこともできます。
  コンソールにログ出力します。

JS 関数のカリー化の利点:

  • 計算は遅延される可能性があります。つまり、カリー化された関数がパラメータ付きで呼び出されると、パラメータは保存用の配列に追加され、パラメータが渡されないときに呼び出されます。
  • パラメータの再利用: 同じ関数が複数回呼び出され、渡されるパラメータのほとんどが同じである場合、その関数はカリー化に適している可能性があります。

世の中のあらゆるものは相対的であり、原因と結果があり、もちろん、カリー化があれば、デカリー化も必ずある。

アンカリー化は、文字通りカリー化の反対です。実は、アンカリー化の本当の目的は、適用範囲の拡大です。つまり、メソッドを呼び出すときに、設計プロセスでオブジェクト自体にこのメソッドがあるかどうかを考慮する必要はありません。メソッドが適用可能であれば、それを使用できます。(動的言語におけるダックタイピングの考え方はこちら)

JS のアンチカリー化について学ぶ前に、まずは動的言語のダックタイピングの考え方について学んで、理解を深めましょう。

動的言語ダックタイピングのアイデア (Wikipedia の説明):

プログラミングにおいて、ダックタイピングは動的型付けのスタイルです。

このスタイルでは、オブジェクトの有効なセマンティクスは、特定のクラスからの継承や特定のインターフェースの実装ではなく、現在のメソッドとプロパティのセットによって決定されます。

この概念の名前は、ジェームズ・ウィットコム・ライリーが提唱したダックテストに由来しています。「ダックテスト」は次のように表現できます。

アヒルのように歩き、アヒルのように泳ぎ、アヒルのように鳴く鳥を見たら、その鳥はアヒルと呼ぶことができます。

理論的な説明は、無味乾燥で理解しにくいことが多い。人間に例えると、あなたは母親の息子/娘である。優秀であろうと、美人であろうと、母親の実の子である限り、あなたは母親の息子/娘である。アヒルのタイプに例えると、アヒルのようにクワクワと鳴き、アヒルのように歩くことができれば、アヒルのように振る舞うことができれば、アヒルであるかどうかに関係なく、アヒルと呼ばれることができる。

Javascript にはダック型の参照がたくさんあります。たとえば、変数に値を割り当てる場合、当然、変数の型を考慮する必要はありません。これが Javascript がより柔軟である理由であり、Javascript は典型的な動的型付け言語です。

デカリー化でダック型がどのように参照されるかを見てみましょう。

//関数プロトタイプオブジェクトにuncurringメソッドを追加する Function.prototype.uncurring = function() {
// this のポインタを変更します //ここでは this は Array.prototype.push を指します
  var self = this;
    //ここでのクロージャは内部関数の実行を返すために使用されます return function() {
    //変数を作成し、配列のプロトタイプオブジェクトにshiftを追加し、最初のパラメータを削除します //配列thisをargumentsに変更します
    var obj = Array.prototype.shift.call(引数);
    //最後に実行に戻り、メソッドを引数であるobjを指すように変更します。
   // 引数をパラメータとして渡します。 return self.apply(obj, arguments);
  };
};

//配列プロトタイプ オブジェクトにアンカリング メソッドを追加します。var push = Array.prototype.push.uncurring();

//テスト //匿名関数の自己実行(function() {
    // ここでの push は関数メソッドです // これは、arguments と 4 の 2 つのパラメーターを渡すことに相当します。ただし、上記の shift メソッドでは、最初のパラメーターが削除され、ここでの arguments パラメーターはインターセプトされるため、最終的に実際に渡されるのは 4 だけです。
  プッシュ(引数、4);
  console.log(引数); //[1, 2, 3, 4]
//無名関数は自身を呼び出し、パラメータ 1、2、3 を受け取ります
})(1, 2, 3)

この時点で、考えてみてください。Arguments はパラメータを受け取るオブジェクトであり、その中に push メソッドはありません。では、なぜ arguments が push メソッドを呼び出すことができるのでしょうか?

これは、コード var push = Array.prototype.push.uncurring(); が配列プロトタイプ オブジェクトの push メソッドに uncurring メソッドを追加し、匿名関数メソッド push(arguments, 4); を実行するためです。実際には、上記のメソッドを呼び出して、Function のプロトタイプ オブジェクトに uncurring メソッドを追加し、実行するクロージャ内部関数を返します。実行プロセス中に、Array プロトタイプ オブジェクトの shift メソッドが push(arguments, 4); の引数をインターセプトします。したがって、実際のメソッド呼び出しは push(4) であり、最終結果は [1,2,3,4] になります。

「JavaScript デザインパターンと開発プラクティス」という本では、JS 関数のアンチカリー化の事例が次のように書かれています。

//オブジェクトを定義する var obj = {
    "長さ":1,
    "0":1
}
//関数プロトタイプオブジェクトでアンカリー化メソッドを定義する
Function.prototype.uncurrying = function() {
    //これはArray.prototype.pushを指します
    var self = this;
    //クロージャは内部関数を返します return function() {
    // これはいくつかの部分に分けることができます // まずapply returnを実行します 
    //Function.prototype.call(Array.prototype.push[obj,2])
   //次にArray.prototype.push.call(obj,2)
    //呼び出しはポインタをobj.push(2)に変更します
    //最終結果は {0: 1, 1: 2, length: 2} になります
        Function.prototype.call.apply(self, arguments) を返します。
}
}

// var push = Array.prototype.push.uncurrying() 内

プッシュ(オブジェクト、2) 
コンソールにログ出力します。
//{0: 1, 1: 2, 長さ: 2}

上記の方法はわかりにくいですか?問題ありません。わかりやすく説明しましょう。

Function.prototype.unCurrying = function() {
    var self = this;
    関数を返す(){
    //[].slice.call(引数,1)===Array.prototype.push.slice.call(引数,1)===引数.slice(1)
self.apply(arguments[0], [].slice.call(arguments, 1)) を返します。
    };
};



var push = Array.prototype.push.uncurrying()
コンソールにログを出力します。
push(obj,2) //{0: 1, 1: 2, 長さ: 2}
コンソールにログ出力します。

分析してみましょう:

1. まず、すべての関数がそれを借用できるように、Function プロトタイプ オブジェクトにアンカリー化メソッドを追加します。

2. クロージャ内部関数を返す

3. クロージャ関数によって返される結果は呼び出しメソッドであり、self は Array.prototype.push を指し、apply メソッドの最初のパラメータは変更ポイントです。次の push(obj,2) は、ポイントを obj.push(2) に変更することと同じです。

4. applyメソッドの2番目のパラメータのcallメソッドはargumentsを指すように変更され、sliceメソッドはargumentsで使用できるようになりました。これはarguments.slice(1)と同じです。

上記は、Javascript で関数のカリー化とデカリー化を実装する方法の詳細です。Javascript 関数のカリー化とデカリー化の詳細については、123WORDPRESS.COM の他の関連記事に注目してください。

以下もご興味があるかもしれません:
  • フロントエンドJavaScriptは関数のカリー化を完全に理解している
  • JavaScript関数カリー化の実装原理とプロセス
  • JavaScript 関数のカリー化の簡単な分析
  • js関数カリー化の使用方法と例の分析
  • JavaScriptは関数のカリー化とデカリー化のプロセス分析を実装します
  • JavaScript 関数カリー化の原理と使用法の分析
  • JS における bind メソッドと関数のカリー化に関する簡単な説明
  • Javascript のクロージャと関数カリー化の簡単な分析
  • JavaScript 関数のカリー化の説明
  • JavaScript 関数のカリー化

<<:  MySQL 最適化 Zabbix パーティション最適化

>>:  Tomcat サーバーが tomcat7w.exe を開けない場合の解決策

推薦する

CentOS7 で docker を使用して Apollo 構成センターをデプロイする実装

Apollo オープンソース アドレス: https://github.com/ctripcorp/...

Linux にバイナリ MySQL をインストールして MySQL パスワードをクラックする方法

1. システムに必要な libaio ソフトウェアがインストールされていることを確認します。インスト...

JS addEventListener() およびattachEvent() メソッドは登録イベントを実装します

JavaScript の DOM イベント モデルでは、オブジェクトの addEventListen...

VMware を使用して PXE バッチ インストール サーバーをテストする詳細なプロセス

目次1. 準備1. 環境を整える2. インストール方法3. ネットワークカードの構成2. インストー...

JS がビデオ弾幕効果を実現

これを実現するには、ES6 モジュール開発とオブザーバー モードを使用します。オブザーバー パターン...

XHTML と CSS によるオブジェクト指向プログラミング

<br />XHTML と CSS がオブジェクト指向だったらよかったのに。 。太陽は北...

NginxとLuaによるグレースケールリリースの実装

memcachedをインストールする yum インストール -y memcached #memcac...

Linux システム AutoFs 自動マウント サービスのインストールと構成

目次序文1. サービスプログラムをインストールする2. メイン設定ファイルを書く3. サブ構成ファイ...

WeChatミニプログラムの基本チュートリアル:Echartの使用

序文まずは最終的な効果を見てみましょう。私が自分で作った小さなデモです。まずEChartsの公式サイ...

選択ドロップダウンメニューのテキストを左右にスクロールするように設定する

marquee タグを使用してフォントのスクロールを設定したいです。コードは次のように記述しましたが...

HTML ページに SVG を挿入する複数の方法

SVG (Scalable Vector Graphics)は、XML 構文に基づいた画像形式です。...

HTML における要素の水平および垂直中央揃えに関する議論

ページをデザインするときには、ログイン ウィンドウを中央に配置するなど、DIV を中央に配置し、ペー...

MySQL の order by ステートメントの最適化方法の詳細な説明

この記事では、ORDER BY文の最適化について学びます。その前に、インデックスの基礎的な理解が必要...

IEのクラッシュバグ

コードをコピーコードは次のとおりです。 <スタイル タイプ="text/css&qu...