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 を使用して等アスペクト比のアダプティブ コンテナを実装する方法

推薦する

CentOS 7のインストールと設定方法のグラフィックチュートリアル

この記事は、CentOS 7の詳細なインストールチュートリアルを参考のために記録します。具体的な内容...

MySQL の一般的なツール例の概要 (推奨)

序文この記事では主にMySQLでよく使われるツールに関する関連コンテンツを紹介し、皆さんの参考と学習...

CSS3で実装されたサムネイルホバー効果

成果を達成する実装コードhtml <ヘッダー> <h1><em>...

MySQL 外部キー設定方法の例

1. 外部キーの設定方法1. MySQL では、2 つのテーブルを関連付けるために、外部キー (FO...

MySQL がデフォルトの分離レベルとして繰り返し読み取りを選択する理由

目次Oracle 分離​​レベルMySQL 分離レベル要約する多くの読者は、MySQL のトランザク...

UnityはMySQLに接続し、テーブルデータの実装コードを読み取ります

表は以下のとおりです。 Unity が読み取って呼び出すときのコード: データベース内の別のテーブル...

Vueは、商品の数を制御するためのコンポーネントのパッケージ化と使用を実装します。

Vueのコントロール商品数量コンポーネントのカプセル化と使用は参考までに。具体的な内容は以下のとお...

Mysql のフィールドのデータの一部をバッチ置換する (推奨)

MYSQL のフィールドのデータの一部をバッチで置き換えます。具体的な導入は次のとおりです。 1....

Nginx フォワード プロキシとリバース プロキシ、および負荷分散機能の構成コード例

この記事は主に、Nginx のフォワード プロキシとリバース プロキシ、および負荷分散機能の設定コー...

Centos7 MySQL データベースのインストールと設定のチュートリアル

1. システム環境yum updateアップグレード後のシステムバージョンは[root@yl-web...

HTMLウェブページテーブル構造化マークアップの応用に関する簡単な説明

Web テーブルの構造マークアップについて説明する前に、いくつかの画像を見てみましょう。 HTML ...

JavaScript を学ぶときに知っておくべき 3 つのヒント

目次1. 魔法の拡張演算子1. 配列をコピーする2. 配列を結合する3. オブジェクトを展開する2....

高品質なコードを書く Web フロントエンド開発実践書の抜粋

(P4) Web 標準は一連の標準で構成されています。中心となる概念は、Web ページの構造、スタイ...

JavaScript DOM オブジェクト操作

目次1. コア1. Domノードを取得する2. ノードの更新2.1 実践演習3. Domノードを削除...

JavaScriptの基礎を学ぶ

目次1. JavaScriptを記述する場所2. JavaScriptでよく使われる入力文と出力文1...