フロントエンドJavaScriptは関数のカリー化を完全に理解している

フロントエンドJavaScriptは関数のカリー化を完全に理解している

1. カレーとは何か

数学とコンピュータ サイエンスにおいて、カリー化とは、複数の引数を取る関数を、単一の引数を取る一連の関数に変換する手法です。

たとえば、3 つのパラメータを取る通常の関数の場合、カリー化後、カリー化されたバージョンの関数は 1 つのパラメータを取り、次のパラメータを取る関数を返します。この関数は 3 番目のパラメータを取る関数を返します。 最後の関数は、3 番目のパラメータを受け取った後、以前に受け取った 3 つのパラメータを元の通常の関数に適用し、最終結果を返します。

数学と計算科学におけるカリー化:

// 数学と計算科学におけるカリー化:

//3つのパラメータを受け取る通常の関数 function sum(a,b,c) {
    コンソールログ(a+b+c)
}

//通常の関数をカリー化されたバージョンに変換するために使用されるツール関数 function curry(fn) {
  //... 内部実装は省略され、新しい関数が返されます }

//カリー化された関数を取得します。let _sum = curry(sum);

// 2番目のパラメータを受け取る関数を返します。let A = _sum(1);
//3番目のパラメータを受け取る関数を返します。let B = A(2);
//最後のパラメータを受け取り、以前のすべてのパラメータを元の関数に適用し、B(3)を実行します // print : 6

Javascript言語の場合、私たちがよく話題にする関数のカリー化の概念は、数学やコンピューター サイエンスにおけるカリー化の概念とまったく同じではありません。

数学とコンピュータ サイエンスでは、カリー化された関数には一度に 1 つの引数しか渡すことができません。

実際のJavascriptアプリケーションのカリー化された関数は、1 つ以上のパラメーターを渡すことができます。

次の例を見てみましょう。

//通常の関数 function fn(a,b,c,d,e) {
  コンソールログ(a,b,c,d,e)
}
//生成されたカリー関数 let _fn = curry(fn);

_fn(1,2,3,4,5); // 出力: 1,2,3,4,5
_fn(1)(2)(3,4,5); // 出力: 1,2,3,4,5
_fn(1,2)(3,4)(5); // 出力: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // 印刷: 1,2,3,4,5

カリー化された_fn関数の場合、受け取ったパラメータの数が元の関数の仮パラメータの数と同じであれば元の関数が実行され、受け取ったパラメータの数が元の関数の仮パラメータの数より少ない場合は、受け取ったパラメータの数が仮パラメータの数と同じになるまで残りのパラメータを受け取る関数が返され、元の関数が実行されます。

カリー化とは何かがわかったところで、カリー化が何のために使用されるのかを見てみましょう。

2. カレーの用途

カリー化は実際には単純な答えを複雑にしますが、同時に関数を使用する際の自由度が高まります。 ここでの関数パラメータの自由な処理がカリー化の核心です。 カリー化の本質は、一般性を減らして適用性を高めることです。例を見てみましょう:

私たちの仕事では、電話番号、メールアドレス、ID番号、パスワードなどの検証など、正規表現の検証を必要とするさまざまな要件に遭遇します。このとき、検証する正規表現オブジェクトと検証する文字列の 2 つのパラメータを受け取る一般的な関数checkByRegExpをカプセル化します。

関数 checkByRegExp(regExp,文字列) {
    regExp.test(文字列)を返します。  
}

checkByRegExp(/^1\d{10}$/, '18642838455'); // 電話番号をチェック checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); // メールアドレスをチェック

一見すると、上記のコードは問題なく、正規表現テストに合格するためのすべてのニーズを満たすことができます。 しかし、次の質問を考えてみましょう。複数の電話番号や複数のメールアドレスを確認する必要がある場合はどうなるでしょうか?

次のようにするかもしれません:

: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :

チェックを行うたびに正規表現の文字列を入力する必要があります。同じ種類のデータをチェックする場合、同じ正規表現を複数回記述する必要があり、使用時に非効率的です。また、 checkByRegExp関数自体はツール関数であり意味がないため、しばらくしてからこれらのコードを再度確認したときにコメントがない場合は、電話番号をチェックしているのか、メールアドレスをチェックしているのか、それとも他の何かをチェックしているのかを知るために、正規表現の内容を確認する必要があります。

この時点で、カリー化を使用してcheckByRegExp関数をカプセル化し、コードの記述を簡素化してコードの読みやすさを向上させることができます。

//カリー化を実行します。let _check = curry(checkByRegExp);
//電話番号を確認するツール関数を生成します。let checkCellPhone = _check(/^1\d{10}$/);
//メールレットを検証するためのツール関数を生成します。checkEmail = _check(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/);

checkCellPhone('18642838455'); // 電話番号を確認checkCellPhone('13109840560'); // 電話番号を確認checkCellPhone('13204061212'); // 電話番号を確認checkEmail('[email protected]'); // メールを確認checkEmail('[email protected]'); // メールを確認checkEmail('[email protected]'); // メールを確認

カプセル化をカリー化した後、コードが簡潔で直感的になるかどうかを確認してみましょう。

カリー化後、 checkCellPhone 和checkEmail, checkCellPhone関数は渡された文字列が電話番号であるかどうかのみを検証でき、 checkEmail関数は渡された文字列が電子メール アドレスであるかどうかのみを検証できます。 元の関数checkByRegExpと比較すると、機能の汎用性は低下しますが、適用性は向上します。 このカリー化の使用は次のように理解できる:パラメータの再利用

別の例を見てみましょう

次のようなデータがあるとします。

リスト = [
    {
        名前: 'ルーシー'
    },
    {
        名前: 'ジャック'
    }
]

データ内のすべての名前属性の値を取得する必要があります。通常は次のようにします。

名前をリスト.map(function(item) {
  item.name を返します。
})

では、これをカリー化された思考でどのように実装するのでしょうか?

prop = curry(function(key,obj) {
    obj[キー]を返します。
})
名前をリスト.map(prop('name'))とします。

これを見て、疑問に思うかもしれません。 nameの属性値を取得するだけの簡単な例なのに、なぜprop関数を実装する必要があるのでしょうか。これは面倒すぎます。

考え方を変えると、 prop関数は一度実装すると、将来何度も使用される可能性があります。したがって、コードの複雑さを考慮すると、 prop関数の実装を削除できます。

実際のコードはlet names = list.map(prop('name')) 1行だけで理解できます。

つまり、カリー化によって、コードはより簡潔になり、読みやすくなりました。

3. カリー化ユーティリティ関数をカプセル化する方法

次に、 curry関数の実装方法を考えてみましょう。

以前のカリー化の定義を思い出してください。カリー化では、いくつかのパラメータを受け取り、残りのパラメータを受け取る関数を返し、十分なパラメータを受け取った後に元の関数を実行します。

カリー化された関数が十分なパラメータを受け取ると元の関数が実行されることは既にわかっていますが、十分なパラメータに達したかどうかをどのように判断するのでしょうか?

私たちには 2 つのアプローチがあります:

  1. 関数の長さプロパティを通じて関数の仮パラメータの数を取得します。仮パラメータの数は、必要なパラメータの数です。
  2. カリー化されたユーティリティ関数を呼び出すときに必要な引数の数を手動で指定します。

これら 2 つのポイントを組み合わせて、単純なcurry関数を実装します。

/**
 * 関数をカリー化します * @param fn カリー化される元の関数 * @param len 必要なパラメータの数。デフォルトは元の関数の仮パラメータの数です */
関数curry(fn,len = fn.length) {
    _curry.call(this,fn,len) を返します
}

/**
 * 転送関数 * @param fn カリー化される元の関数 * @param len 必要なパラメータ数 * @param args 受信したパラメータリスト */
関数_curry(fn,len,...args) {
    関数を返す (...パラメータ) {
        _args = [...args,...params] とします。
        if(_args.length >= len){
            fn.apply(this,_args) を返します。
        }それ以外{
            _curry.call(this,fn,len,..._args) を返します。
        }
    }
}

これを検証してみましょう:

_fn = curry(function(a,b,c,d,e){
    コンソールログ(a,b,c,d,e)
});

_fn(1,2,3,4,5); // 出力: 1,2,3,4,5
_fn(1)(2)(3,4,5); // 出力: 1,2,3,4,5
_fn(1,2)(3,4)(5); // 出力: 1,2,3,4,5
_fn(1)(2)(3)(4)(5); // 印刷: 1,2,3,4,5

よく使用されるツール ライブラリlodashcurryメソッドを提供し、プレースホルダーを使用して受信パラメーターの順序を変更する非常に興味深いplaceholder関数を追加します。

たとえば、プレースホルダーを渡すと、この呼び出しで渡されるパラメーターはプレースホルダーをスキップし、プレースホルダーには次の呼び出しのパラメーターが次のように入力されます。

公式サイトの例を見てみましょう。

次にプレースホルダー機能の実装方法を考えてみましょう。

lodashcurry関数の場合、 curry関数はlodashオブジェクトにマウントされるため、 lodashオブジェクトがデフォルトのプレースホルダーとして使用されます。

私たちが独自に実装したcurry関数はどのオブジェクトにもマウントされていないため、 curry関数をデフォルトのプレースホルダーとして使用します。

プレースホルダを使用する目的は、パラメータを渡す順序を変更することです。そのため、 curry関数の実装では、プレースホルダを使用するかどうかと、プレースホルダが表すパラメータの位置をその都度記録する必要があります。

コード上で直接:

/**
 * @param fn カリー化される関数* @param length 必要なパラメータの数。デフォルトは関数の仮パラメータの数* @param holder プレースホルダ。デフォルトは現在のカリー化関数* @return {Function} カリー化後の関数*/
関数 curry(fn,length = fn.length,holder = curry){
    _curry.call(this,fn,length,holder,[],[]) を返します
}
/**
 * 転送関数* @param fn カリー化の元の関数* @param length 元の関数に必要なパラメータの数* @param holder 受け取ったプレースホルダ* @param args 受け取ったパラメータリスト* @param holders 受け取ったプレースホルダの位置リスト* @return {Function} カリー化を続行する関数または最終結果*/
関数 _curry(fn,長さ,ホルダー,引数,ホルダー){
    関数(..._args)を返す{
        // 同じ関数に対する複数の操作による混乱を避けるためにパラメータをコピーします。let params = args.slice();
        //プレースホルダーの位置リストをコピーし、新しく追加されたプレースホルダーをここに追加します。let _holders = holders.slice();
        //パラメータをループし、パラメータを追加したり、placeholders_args.forEach((arg,i)=>{
            //実パラメータの前にプレースホルダーがあります。プレースホルダーを実パラメータに置き換えます。if (arg !== holder && holders.length) {
                index = holders.shift() とします。
                _holders.splice(_holders.indexOf(インデックス),1);
                パラメータ[インデックス] = 引数;
            }
            //実際のパラメータの前にプレースホルダはありません。パラメータをパラメータリストに追加します。else if (arg !== holder && !holders.length) {
                パラメータをプッシュします。
            }
            //プレースホルダーが渡されます。前にプレースホルダーがない場合は、プレースホルダーの位置を記録します。else if (arg === holder && !holders.length) {
                パラメータをプッシュします。
                _holders.push(params.length - 1);
            }
            //渡されたプレースホルダーの前にプレースホルダーがある場合は、元のプレースホルダーの位置を削除します。そうでない場合は、(arg === holder && holders.length) {
                ホルダー.shift();
            }
        });
        // params の最初の length レコードにはプレースホルダーが含まれていないので、関数 if(params.length >= length && params.slice(0,length).every(i=>i!==holder)){ を実行します。
            fn.apply(this,params); を返します。
        }それ以外{
            _curry.call(this,fn,length,holder,params,_holders) を返します。
        }
    }
}

確認します:

fn = 関数(a, b, c, d, e) {
    コンソールにログ出力します。
}

let _ = {}; // プレースホルダーを定義 let _fn = curry(fn,5,_); // 関数をカリー化し、必要なパラメーターの数を指定し、必要なプレースホルダーを指定します _fn(1, 2, 3, 4, 5); // print: 1,2,3,4,5
_fn(_, 2, 3, 4, 5)(1); // 出力: 1,2,3,4,5
_fn(1, _, 3, 4, 5)(2); // 出力: 1,2,3,4,5
_fn(1, _, 3)(_, 4,_)(2)(5); // 出力: 1,2,3,4,5
_fn(1, _, _, 4)(_, 3)(2)(5); // 出力: 1,2,3,4,5
_fn(_, 2)(_, _, 4)(1)(3)(5); // 出力: 1,2,3,4,5

カレー機能を本格的に実装しました〜〜

フロントエンド JavaScript の関数カリー化を徹底的に理解する方法に関するこの記事はこれで終わりです。JavaScript 関数カリー化の詳細については、123WORDPRESS.COM の以前の記事を検索するか、以下の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

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

<<:  Ubuntu16.04にclionをインストールするプロセス全体と手順の詳細な説明

>>:  MySQLで最新のトランザクションIDを照会する方法

推薦する

WindowsにJDK8をインストールする方法

1. ダウンロード: http://www.oracle.com/technetwork/java/...

一般的な XHTML タグの使用方法の紹介

XHTML には多くのタグがありますが、頻繁に使用されるのはごくわずかであり、習得する必要があるのは...

SpringBoot と Vue の相互作用におけるクロスドメイン問題の解決策

目次ブラウザ同一生成元ポリシー1. VUEフロントエンド構成プロキシはクロスドメインの問題を解決しま...

MySQLでトランザクションを開始する方法

序文この記事では主にMySQLでトランザクションを開始する方法について紹介します。関連情報については...

Docker Compose で利用可能な環境変数の詳細な説明

Compose のいくつかの部分は、何らかの方法で環境変数を扱います。このチュートリアルは、必要な情...

VueはEchartsを使用して3次元棒グラフを実装します

この記事では、Echartsを使用して3次元棒グラフを実装するVueの具体的なコードを参考までに共有...

ES6 クラス継承を使用してゴージャスなボール効果を実現する方法

目次導入実装手順キャンバス環境を作成するライティングボールBallクラスを継承するMoveBallク...

Linuxでファイルを削除してもスペースが解放されない問題の対処方法

問題の背景業務システムのサーバ監視システムからディスク使用率が90%に達したという早期警告通知が来た...

MySQL テーブル分割後にスムーズにオンラインになる方法

目次テーブルの目的例えばテーブル分割戦略すでにオンラインになっている実行中のテーブルはどうすればよい...

Vue を使用した Amap アプリケーション開発のベスト プラクティス

目次序文非同期読み込みパッケージコンポーネントコンポーネントの使用インターフェースをカスタマイズする...

Vueは小さなフォーム検証機能を実装します

この記事では、フォーム検証を実装するためのVueの具体的なコードを例として紹介します。具体的な内容は...

WeChatミニプログラムはどのようにしてユーザー情報とユーザーの電話番号を同時に取得するのか

今日ログインページを書いていたとき、個人情報と携帯電話番号を認証する必要がありましたが、ページにボタ...

GoogleとFacebookがDockerを使わない理由

この記事を書いた理由は、修正した分散 PyTorch プログラムを Facebook のクラスター上...

Vue Notepadの例の詳細な説明

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

Docker+Jenkins+Gitlab+Djangoアプリケーションデプロイ実践の詳細な説明

1. 背景インターネット アプリケーションの急速な更新と反復という状況では、従来の手作業や単純なスク...