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 を開けない場合の解決策

推薦する

VMwareでCentOSがインターネットにアクセスできない問題を素早く解決

昨日、VMware に CentOS7 をインストールしました。Tomcat パッケージを転送するた...

Linuxでpyファイルを直接実行する方法

1. まずファイルを作成します(ファイルを配置するディレクトリにcdします) myTest.py を...

nginx設定ファイルの場所を見つける方法の詳細な説明

よく知らないサーバーの場合や、かなり前にインストールした場所を忘れてしまった場合、構成ファイルの場所...

Vue での this.$set の動的データバインディングのケーススタディ

インターネット上の this.$set の説明はわかりにくいと感じます。単一データ、オブジェクト、配...

MySQLはIDに適切なデータ型を選択します

目次分散IDソリューションの概要データベース自動増分IDデータベースマルチマスターモード数値セグメン...

入力ボックスのコンテンツプロンプトと非表示機能を実装する JavaScript

入力ボックスが小さい場合、内容を入力した後に、入力内容が拡大されたプロンプト ボックスを表示したいこ...

HTML テーブルタグチュートリアル (19): 行タグ

<TR> タグの属性は、次の表に示すように、テーブル内の各行のプロパティを設定するために...

Linux の who コマンド例の紹介

誰についてシステムにログインしているユーザーを表示します。 who コマンドを実行すると、現在システ...

CentOS 6 または CentOS 7 でディスク領域をクリアする方法

以下は、CentOS 6 または CentOS 7 サーバーのディスク領域をクリアするための簡単なコ...

vue $setは配列コレクションオブジェクトへの値の割り当てを実装します

Vue $set 配列コレクションオブジェクトの割り当てVue カスタム配列オブジェクト コレクショ...

MySQLのネストされたトランザクションで発生する問題

MySQL はネストされたトランザクションをサポートしていますが、それを実行する人は多くありません....

MySQLの文字セットと検証ルールの詳細な説明

1いくつかの一般的な文字セットMySQL で最も一般的な文字セットには、ASCII 文字セット、ラテ...

MySQLのconcat関連関数の詳細な説明

1. concat() 関数機能: 複数の文字列を 1 つの文字列に連結する構文: concat(s...

uniapp エントリーレベル nvue クライミングピット記録の分析

目次序文こんにちは世界画像 境界線の半径を設定する実ピクセルを設定する外部CSSをインポートttfフ...

HTML+CSS をベースにした素敵なフリップログインおよび登録インターフェースを作成します

素敵なフリップログインと登録インターフェースを作成する序文最近、ネットワーク ディスクを構築しようと...