JavaScript 関数のカリー化

JavaScript 関数のカリー化

1 関数カリー化とは何ですか?

コンピュータ サイエンスにおいて、 Currying 、複数の引数を受け入れる関数を、単一の引数 (元の関数の最初の引数) を受け入れ、残りの引数を受け入れて結果を返す新しい関数に変換する手法です。この手法は論理学者Haskell Curryにちなんで名付けられました。

どういう意味ですか?簡単に言えば、カリー化は複数のパラメータを持つ関数を変換するために使用される手法です。

例えば:

// これは3つのパラメータを受け入れる関数です。const add = function(x, y, z) {
  x + y + zを返す
}


これを変換すると、次のような関数が得られます。

// 単一のパラメータを受け入れる const curryingAdd = function(x) {
  // 残りのパラメータを受け入れる関数を返します return function(y, z) {
    x + y + zを返す
  }
}


これによって何が変わるのでしょうか?通話から比較:

// 追加を呼び出す
加算(1, 2, 3)
 
// curryingAdd を呼び出す
カレー化追加(1)(2, 3)
// もっとわかりやすく見てみましょう。これはconst fn = curryingAdd(1)と同等です。
関数(2, 3)

ご覧のとおり、変換された関数はバッチでパラメータを受け入れることができます。その有用性については後で説明するので、まずこの点に留意してください。 fn( curryingAdd返される関数)もさらに変換できる。

次のように:

const curryingAdd = function(x) {
  関数(y)を返す{
    関数(z)を返す{
      x + y + zを返す
    }
  }
}
// curryingAdd(1)(2)(3) を呼び出す
// つまり、const fn = curryingAdd(1)
定数fn1 = fn(2)
fn1(3)

上記の 2 つの変換プロセスは関数カリー化です。

簡単に言えば、マルチパラメータ関数fいくつかのパラメータを受け入れる関数gに変換し、この関数g他のパラメータを受け入れるために使用される関数hを返します。関数 h はさらにカリー化できます。それは入れ子人形のプロセスです。

では、関数をカリー化するためにそこまで苦労する意味は何でしょうか?

2 カレーの役割と特徴

2.1 パラメータの再利用

職場で遭遇する要件:正規表現を使用して電話番号、メールアドレス、ID カードなどの合法性をチェックする

そこで、検証関数を次のようにカプセル化します。

/**
 * @description 正規表現検証文字列を渡します* @param {RegExp} regExp 正規表現オブジェクト* @param {String} str 検証する文字列* @return {Boolean} 検証に合格したかどうか*/
関数 checkByRegExp(regExp, str) {
    regExp.test(str) を返す
}

多数の携帯電話番号とメールアドレスを検証したい場合は、次のように呼び出します。

// 電話番号を確認します checkByRegExp(/^1\d{10}$/, '15152525634'); 
checkByRegExp(/^1\d{10}$/, '13456574566'); 
checkByRegExp(/^1\d{10}$/, '18123787385'); 
// メールをチェック checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); 
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]'); 
checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/, '[email protected]');

問題はなさそうだが、まだ改善の余地がある

  • 同じタイプのデータを検証する場合、同じ正規表現を何度も記述します。
  • コードはあまり読みやすくありません。コメントがないと、正規表現の役割をすぐには理解できません。

関数のカリー化を使用してこれを改善してみましょう。

// 関数をカリー化する function checkByRegExp(regExp) {
    関数(str)を返す{
        regExp.test(str) を返す
    }
}


したがって、異なる通常のオブジェクトを渡すと、異なる機能を持つ関数を取得できます。

// 電話番号をチェック const checkPhone = curryingCheckByRegExp(/^1\d{10}$/)
// メールをチェック const checkEmail = curryingCheckByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/)


携帯電話と電子メール アドレスを確認するためのコードがよりシンプルで読みやすくなりました。

// 電話番号を確認する checkPhone('15152525634'); 
電話番号を確認してください('13456574566'); 
電話番号を確認してください('18123787385'); 
// メールを確認する checkEmail('[email protected]'); 
メールアドレスを確認してください('[email protected]'); 
メールアドレスを確認してください('[email protected]');

これはパラメータの再利用です。特定の関数を持つ関数を直接呼び出すには、最初のパラメータregExp再利用するだけで済みます。

ユニバーサル関数 ( checkByRegExpなど) は互換性の問題を解決しますが、不便さももたらします。たとえば、異なるアプリケーション シナリオでは、問題を解決するために複数の異なるパラメーターを渡す必要があります。

同じルールが繰り返し使用される場合(携帯電話のパラメータの検証など)、コードの重複が発生することがあります。カリー化により重複を排除し、パラメータの再利用の目的を達成できます。

カリー化の重要な考え方:適用範囲を狭め、適用性を高める

2.2 早期復帰

JS DOMイベント リスナーでは、要素にイベント ハンドラーを追加するためにaddEventListenerメソッドを使用しますが、一部のブラウザー バージョンではこのメソッドがサポートされていないため、代わりにattachEventメソッドを使用します。

今回は、各ブラウザのバージョンと互換性のあるコードを記述します。

/**
 * @説明: 
 * @param {object} element DOM要素オブジェクト* @param {string} type イベントタイプ* @param {Function} fn イベント処理関数* @param {boolean} isCapture キャプチャするかどうか* @return {void}
 */
関数 addEvent(要素, タイプ, fn, isCapture) {
    ウィンドウにイベントリスナーを追加する場合
        要素.addEventListener(type, fn, isCapture)
    } それ以外の場合 (window.attachEvent) {
        要素.attachEvent("on" + タイプ, fn)
    }
}

イベントリスナーの追加にはaddEventを使用しますが、このメソッドが呼び出されるたびに判定が行われます。実際にはブラウザのバージョンが判別された後は、繰り返し判定する必要はありません。

カレー作り:

関数curryingAddEvent() {
    ウィンドウにイベントリスナーを追加する場合
        関数(要素、タイプ、fn、isCapture)を返す{
            要素.addEventListener(type, fn, isCapture)
        }
    } それ以外の場合 (window.attachEvent) {
        関数(要素、型、関数)を返す{
            要素.attachEvent("on" + タイプ, fn)
        }
    }
}
const addEvent = curryingAddEvent()
 
// 即時実行関数を使用して上記のコードをマージすることもできます const addEvent = (function curryingAddEvent() {
  ...
})()

ここで取得するaddEvent判断後に取得される関数であり、今後の呼び出しで判断を繰り返す必要はありません。

これは早期リターンまたは早期確認です。カリー化後、関数はいくつかのタスクを事前に処理し、他のタスクを処理する関数を返すことができます。

さらに、 curryingAddEventパラメータを受け入れないようです。これは、元の関数の条件 (つまり、ブラウザのバージョンがaddEventListenerをサポートしているかどうか) がグローバルから直接取得されるためです。

論理的には、次のように変更できます。

mode = window.addEventListener ? 0 : 1 とします。
関数 addEvent(モード、要素、タイプ、fn、isCapture) {
  モード === 0 の場合
    要素にイベントリスナーを追加します(type、fn、isCapture);
  } それ以外の場合 (モード === 1) {
    要素.attachEvent("on" + タイプ、fn);
  }
}
// この方法では、カリー化後に最初にパラメータを受け入れることができます。 function curryingAddEvent(mode) {
    モード === 0 の場合
        関数(要素、タイプ、fn、isCapture)を返す{
            要素.addEventListener(type, fn, isCapture)
        }
    } それ以外の場合 (モード === 1) {
        関数(要素、型、関数)を返す{
            要素.attachEvent("on" + タイプ, fn)
        }
    }
}

もちろんこれを変更する必要はありません。

2.3 遅延実行

実際、遅延実行は、上記の通常の検証とイベント リスニングの例にすでに反映されています。

curryingCheckByRegExp関数はcheckPhonecheckEmail関数を呼び出した後に返します。

addEvent関数はcurringAddEvent関数が呼び出された後に返されます。

返された関数はすぐには実行されず、呼び出しを待機します。

3 一般的なカリー化ユーティリティ関数のカプセル化#

上記では、カリー化のために元の関数を手動で変更し、 add curryingAddcheckByRegExpcurryingCheckByRegExpに、 addEventcurryingAddEventに変更しました。

関数をカリー化するたびに、基礎となる関数を手動で変更する必要がありますか?もちろん違います

一般的なカリー化ユーティリティ関数をカプセル化することができます(インタビュー用の手書きコード)

/**
 * @description: 関数をカリー化するためのツール関数* @param {Function} fn カリー化される関数* @param {array} args すでに受け取った引数のリスト* @return {Function}
 */
const カリー化 = function(fn, ...args) {
    // fn に必要なパラメータの数 const len ​​= fn.length
    // 残りのパラメータを受け取る関数を返す return function (...params) {
        // 受信したパラメータリストと新しく受信したパラメータリストを連結する let _args = [...args, ...params]
        // 受け取ったパラメータの数が足りない場合は、残りのパラメータを受け取るための新しい関数を返し続けます if (_args.length < len) {
            currying.call(this, fn, ..._args) を返します
        }
       // すべてのパラメータを受け取ったら、元の関数を呼び出します。 return fn.apply(this, _args)
    }
}

このカリー化ユーティリティ関数は、いくつかのパラメータを受け取り、残りのパラメータを待機する新しい関数を返し、必要なパラメータがすべて受け取られるまで再帰的に実行し、 applyを通じて元の関数を呼び出すために使用されます。

今では、関数をカリー化するために元の関数を手動で変更する必要がなくなりました。

// ツール関数を直接使用して、電話番号とメールアドレスを確認する関数を返します。const checkPhone = currying(checkByRegExp(/^1\d{10}$/))
const checkEmail = カリー化(checkByRegExp(/^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/))


しかし、上記のイベント監視の例は、このユーティリティ関数を使用してカリー化することはできません。理由は前述のとおりです。その条件はグローバルコンテキストから直接取得されるため、非常に特殊です。外部から条件を渡すように変更すると、ユーティリティ関数のカリー化を使用できます。もちろん、これは必要ありません。元の関数を直接変更する方が直接的で読みやすくなります。

4 要約と補足

  • カリー化は、適用範囲を狭め、適用性を向上させるという重要な考え方を強調しています。
  • カリー化の3つの機能と特徴: パラメータの再利用、早期復帰、遅延実行
  • カリー化はクロージャの典型的な応用であり、クロージャを使用してメモリに格納されるスコープを形成し、受信したパラメータの一部をこのスコープに保存して後で使用するものです。残りのパラメータを受け取るための新しい関数を返す

JavaScript関数カリー化に関するこの記事はこれで終わりです。関数カリー化についてさらに詳しく知りたい方は、123WORDPRESS.COM の過去の記事を検索するか、以下の関連記事を引き続きご覧ください。今後とも 123WORDPRESS.COM をよろしくお願いいたします。

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

<<:  MySQLのMVCCマルチバージョン同時実行制御の実装

>>:  CSS を使用して等アスペクト比のアダプティブ コンテナを実装する方法

推薦する

Ubuntu 20.04 IPアドレスを変更する方法の例

例:本日、前回のオフィスコラボレーションプラットフォーム実験の続きをしていたところ、仮想マシンは以前...

JavaScript で外部変数にアクセスするサブ関数の 3 つのソリューション

序文Web ページを作成するときに、次のような状況に遭遇することはよくあります。 <本文>...

LED を使って Linux カーネルを使い始める方法を探る

目次序文LEDトリガー探索を始めるLEDデバイス登録LEDディレクトリ類推によって理解するクラスディ...

nginx ウェブサイト サービスのアンチホットリンクを設定する方法 (推奨)

1. ホットリンクの原則1.1 Webページの準備Web ソース ホスト (192.168.153...

JavaScriptはPromiseを使用して複数の繰り返しリクエストを処理します

1. なぜこの記事を書くのですか?重複リクエストの処理に関する記事をたくさん読んだことがあるでしょう...

Linux ディスク クォータ管理のグラフィカルな例

ディスク クォータは、コンピューター内の指定されたディスクのストレージ制限です。つまり、管理者はユー...

反応ループデータの実装(リスト)

まず、バックグラウンドから来るデータをシミュレートしてみましょう。ここでは、コードをわかりやすくする...

js を使ってシンプルな虫眼鏡効果を実現

この記事の例では、参考までに簡単な虫眼鏡効果を実現するためのjsの具体的なコードを共有しています。具...

Linux カーネル デバイス ドライバー キャラクタ デバイス ドライバー ノート

/******************** * キャラクターデバイスドライバー**********...

CentOS システムの rpm インストールと Nginx の設定

目次CentOS rpm のインストールと Nginx の設定導入rpm パッケージのインストールサ...

複数の HTML ページで HTML コードをまとめて呼び出す方法

方法 1: スクリプト方式を使用する:共通ヘッダー ファイル head.js または共通フッター フ...

Vue3.0 + TypeScript + Vite初体験の詳しい説明

目次プロジェクトの作成プロジェクト構造メイン.jsアプリ.vue:設定コンポジションAPI参照反応的...

VUE v-for の :key の詳細な説明

v-for タグにキーが追加されていない場合。 <!DOCTYPE html> <...

CMD で MySQL データベースを操作するときに中国語の文字化けが発生する問題の解決方法

Baiduで検索しました。 。 chcp コマンドを使用して、cmd の文字エンコーディングを 65...

SQL Server コメントのショートカット キー操作

SQL Server のバッチコメントバッチ注釈Ctrl + (K, C): Ctrlキーを押しなが...