ホームページ ウェブフロントエンド jsチュートリアル フロントエンドの上級基礎(8):関数のカリー化を徹底解説

フロントエンドの上級基礎(8):関数のカリー化を徹底解説

Feb 27, 2017 pm 02:25 PM
フロントエンド ベース カリー化 高度な

カリー化は比較的高度な関数の応用であり、理解するのは簡単ではありません。なので、もっとわかりやすく表現するにはどうすればいいのか、ずっと考えていました。長い間考えた結果、カリー化という概念を脇に置いて、重要だが見落とされがちな 2 つの知識ポイントを追加することにしました。


1. 関数の暗黙的変換に関する補足知識

JavaScript は弱い型付け言語であり、その暗黙的変換は非常に柔軟で興味深いものです。暗黙的な変換を深く理解していない場合、4 + true = 5 などの一部の演算の結果に混乱する可能性があります。もちろん、暗黙的な変換を十分に理解していれば、js の使用能力を大幅に向上できることは間違いありません。ただ、暗黙的な変換ルールをすべて共有するつもりはありません。ここでは、関数の暗黙的な変換に関するいくつかのルールのみを共有します。

簡単な思考の質問をしてみましょう。

function fn() {
    return 20;
}
console.log(fn + 10); // 输出结果是多少?
ログイン後にコピー

いくつかの変更を加えて、出力がどのようになるかを考えてみませんか?

rreee

引き続き変更できます。

function fn() {
    return 20;
}
fn.toString = function() {
    return 10;
}
console.log(fn + 10);  // 输出结果是多少?
ログイン後にコピー
function fn() {
    return 20;
}

fn.toString = function() {
    return 10;
}

fn.valueOf = function() {
    return 5;
}

console.log(fn + 10); // 输出结果是多少?
ログイン後にコピー

console.log を使用するとき、または操作を実行するときに、暗黙的な変換が発生する可能性があります。上記の 3 つの例から、関数の暗黙的な変換についていくつかの結論を引き出すことができます。

toString と valueOf を再定義しない場合、関数の暗黙的な変換により、デフォルトの toString メソッドが呼び出され、関数の定義内容が文字列として返されます。 toString/vauleOf メソッドを積極的に定義すると、暗黙的な変換の戻り結果は自分自身で制御されます。 valueOf の優先順位は toString よりも高くなります。

つまり、上記の例の結論は理解しやすいです。試してみることをお勧めします。


2. 補足知識ポイント: call/apply を使用して配列をシールするマップメソッド

map(): 配列内の各項目に対して指定された関数を実行し、各関数を返す結果の配列を呼び出します。

平たく言えば、配列の各要素を走査し、マップの最初のパラメーター (コールバック関数) で計算を実行し、計算結果を返すことです。すべての計算結果から構成される新しい配列を返します。

// 输出结果分别为
function fn() {
    return 20;
}10
20
15
ログイン後にコピー

は、上記の例のコメントで、map メソッドの詳細を詳しく説明しました。ここで、マップをどのようにカプセル化するかという問題に直面する必要があります。

まず for ループについて考えてみましょう。 for ループを使用してマップを実装できますが、カプセル化する際にはいくつかの問題を考慮します。 for ループを使うと、確かにループ処理をカプセル化するのは簡単ですが、for ループ内の各項目に対して何をするかが決まったものでカプセル化するのは困難です。すべてのシナリオで、for ループ内のデータの処理が明らかに異なるためです。

そこで、これらのさまざまな操作を別の関数で処理する良い方法をみんなで考え、この関数をマップ メソッドの最初のパラメーターにします。具体的には、このコールバック関数でどのような操作を行うかは私たち次第です。使用するときの判断に。したがって、この考え方に基づくカプセル化の実装は次のようになります。

// 回调函数中有三个参数
// 第一个参数表示newArr的每一项,第二个参数表示该项在数组中的索引值
// 第三个表示数组本身
// 除此之外,回调函数中的this,当map不存在第二参数时,this指向丢失,当存在第二个参数时,指向改参数所设定的对象
var newArr = [1, 2, 3, 4].map(function(item, i, arr) {
    console.log(item, i, arr, this);  // 可运行试试看
    return item + 1;  // 每一项加1
}, { a: 1 })

console.log(newArr); // [2, 3, 4, 5]
ログイン後にコピー

上記のパッケージでは、最初に空の一時配列を定義しました。これは、最終的な戻り結果を格納するために使用されます。 for ループでは、ループするたびにパラメーター fn 関数が 1 回実行され、fn のパラメーターが call メソッドを使用して渡されます。

map のカプセル化プロセスを理解すると、map を使用するときに最初のコールバック関数で常に戻り値を期待する理由が理解できます。 eslintのルールではmapを使用する際に戻り値を設定しないとエラーと判断されます。

さて、関数の暗黙の変換ルールと、このシナリオでの call/apply の使用方法を理解したので、簡単な例を通してカリー化を理解してみましょう。


3. 簡単なものから奥深いものまでのカリー化

フロントエンドのインタビューでは、広く出回っているカリー化に関するインタビューの質問があります。

計算結果が次の期待を満たすことができるように add メソッドを実装します:

Array.prototype._map = function(fn, context) {
    var temp = [];
    if(typeof fn == 'function') {
        var k = 0;
        var len = this.length;
        // 封装for循环过程
        for(; k < len; k++) {
            // 将每一项的运算操作丢进fn里,利用call方法指定fn的this指向与具体参数
            temp.push(fn.call(context, this[k], k, this))
        }
    } else {
        console.error(&#39;TypeError: &#39;+ fn +&#39; is not a function.&#39;);
    }

    // 返回每一项运算结果组成的新数组
    return temp;
}

var newArr = [1, 2, 3, 4].map(function(item) {
    return item + 1;
})
// [2, 3, 4, 5]
ログイン後にコピー

明らかに、計算結果はすべてのパラメーターの合計であり、add メソッドが実行されるたびに同じ関数を返し、継続する必要があります。残りのパラメータを計算します。

最も単純な例から段階的に解決策を見つけることができます。

2回呼び出すだけの場合は、次のようにカプセル化できます。

add(1)(2)(3) = 6
add(1, 2, 3)(4) = 10
add(1)(2)(3)(4)(5) = 15
ログイン後にコピー

3 回のみ呼び出された場合:

function add(a) {
    return function(b) {
        return a + b;
    }
}

console.log(add(1)(2));  // 3
ログイン後にコピー

上記のカプセル化は、必要な結果に多少似ていますが、パラメーターの使用が非常に制限されているため、これは私たちが必要とする最終的な結果ではありません。 。どうすればいいですか?上記 2 つの例を要約すると、実際にはクロージャの特性を使用して、すべてのパラメータを最終的に返される関数に集中させ、計算して結果を返します。したがって、カプセル化する際の主な目的は、計算用のパラメータを集中させることです。

具体的な実装を見てみましょう。

function add() {
    // 第一次执行时,定义一个数组专门用来存储所有的参数
    var _args = [].slice.call(arguments);

    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var adder = function () {
        var _adder = function() {
            [].push.apply(_args, [].slice.call(arguments));
            return _adder;
        };

        // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
        _adder.toString = function () {
            return _args.reduce(function (a, b) {
                return a + b;
            });
        }

        return _adder;
    }
    return adder.apply(null, [].slice.call(arguments));
}

// 输出结果,可自由组合的参数
console.log(add(1, 2, 3, 4, 5));  // 15
console.log(add(1, 2, 3, 4)(5));  // 15
console.log(add(1)(2)(3)(4)(5));  // 15
ログイン後にコピー

上面的实现,利用闭包的特性,主要目的是想通过一些巧妙的方法将所有的参数收集在一个数组里,并在最终隐式转换时将数组里的所有项加起来。因此我们在调用add方法的时候,参数就显得非常灵活。当然,也就很轻松的满足了我们的需求。

那么读懂了上面的demo,然后我们再来看看柯里化的定义,相信大家就会更加容易理解了。

柯里化(英语:Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。

1.接收单一参数,因为要携带不少信息,因此常常以回调函数的理由来解决。

2.将部分参数通过回调函数等方式传入函数中

3.返回一个新函数,用于处理所有的想要传入的参数

在上面的例子中,我们可以将add(1, 2, 3, 4)转换为add(1)(2)(3)(4)。这就是部分求值。每次传入的参数都只是我们想要传入的所有参数中的一部分。当然实际应用中,并不会常常这么复杂的去处理参数,很多时候也仅仅只是分成两部分而已。

咱们再来一起思考一个与柯里化相关的问题。

假如有一个计算要求,需要我们将数组里面的每一项用我们自己想要的字符给连起来。我们应该怎么做?想到使用join方法,就很简单。

var arr = [1, 2, 3, 4, 5];

// 实际开发中并不建议直接给Array扩展新的方法
// 只是用这种方式演示能够更加清晰一点
Array.prototype.merge = function(chars) {
    return this.join(chars);
}

var string = arr.merge(&#39;-&#39;)

console.log(string);  // 1-2-3-4-5
ログイン後にコピー

增加难度,将每一项加一个数后再连起来。那么这里就需要map来帮助我们对每一项进行特殊的运算处理,生成新的数组然后用字符连接起来了。实现如下:

var arr = [1, 2, 3, 4, 5];

Array.prototype.merge = function(chars, number) {
    return this.map(function(item) {
        return item + number;
    }).join(chars);
}

var string = arr.merge(&#39;-&#39;, 1);

console.log(string); // 2-3-4-5-6
ログイン後にコピー

但是如果我们又想要让数组每一项都减去一个数组之后再连起来呢?当然和上面的加法操作一样的实现。

var arr = [1, 2, 3, 4, 5];

Array.prototype.merge = function(chars, number) {
    return this.map(function(item) {
        return item - number;
    }).join(chars);
}

var string = arr.merge(&#39;~&#39;, 1);

console.log(string); // 0~1~2~3~4
ログイン後にコピー

机智的小伙伴肯定发现困惑所在了。我们期望封装一个函数,能同时处理不同的运算过程,但是我们并不能使用一个固定的套路将对每一项的操作都封装起来。于是问题就变成了和封装map的时候所面临的问题一样了。我们可以借助柯里化来搞定。

与map封装同样的道理,既然我们事先并不确定我们将要对每一项数据进行怎么样的处理,我只是知道我们需要将他们处理之后然后用字符连起来,所以不妨将处理内容保存在一个函数里。而仅仅固定封装连起来的这一部分需求。

于是我们就有了以下的封装。

// 封装很简单,一句话搞定
Array.prototype.merge = function(fn, chars) {
    return this.map(fn).join(chars);
}

var arr = [1, 2, 3, 4];

// 难点在于,在实际使用的时候,操作怎么来定义,利用闭包保存于传递num参数
var add = function(num) {
    return function(item) {
        return item + num;
    }
}

var red = function(num) {
    return function(item) {
        return item - num;
    }
}

// 每一项加2后合并
var res1 = arr.merge(add(2), &#39;-&#39;);

// 每一项减2后合并
var res2 = arr.merge(red(1), &#39;-&#39;);

// 也可以直接使用回调函数,每一项乘2后合并
var res3 = arr.merge((function(num) {
    return function(item) {
        return item * num
    }
})(2), &#39;-&#39;)

console.log(res1); // 3-4-5-6
console.log(res2); // 0-1-2-3
console.log(res3); // 2-4-6-8
ログイン後にコピー

大家能从上面的例子,发现柯里化的特征吗?


四、柯里化通用式

通用的柯里化写法其实比我们上边封装的add方法要简单许多。

var currying = function(fn) {
    var args = [].slice.call(arguments, 1);

    return function() {
        // 主要还是收集所有需要的参数到一个数组中,便于统一计算
        var _args = args.concat([].slice.call(arguments));
        return fn.apply(null, _args);
    }
}

var sum = currying(function() {
    var args = [].slice.call(arguments);
    return args.reduce(function(a, b) {
        return a + b;
    })
}, 10)

console.log(sum(20, 10));  // 40
console.log(sum(10, 5));   // 25
ログイン後にコピー


五、柯里化与bind

Object.prototype.bind = function(context) {
    var _this = this;
    var args = [].prototype.slice.call(arguments, 1);
    return function() {
        return _this.apply(context, args)
    }
}
ログイン後にコピー

这个例子利用call与apply的灵活运用,实现了bind的功能。

在前面的几个例子中,我们可以总结一下柯里化的特点:

1.接收单一参数,将更多的参数通过回调函数来搞定?

2.返回一个新函数,用于处理所有的想要传入的参数;

3.需要利用call/apply与arguments对象收集参数;

4.返回的这个函数正是用来处理收集起来的参数。

希望大家读完之后都能够大概明白柯里化的概念,如果想要熟练使用它,就需要我们掌握更多的实际经验才行。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ Mar 16, 2024 pm 12:09 PM

PHP と Vue: フロントエンド開発ツールの完璧な組み合わせ 今日のインターネットの急速な発展の時代において、フロントエンド開発はますます重要になっています。 Web サイトやアプリケーションのエクスペリエンスに対するユーザーの要求がますます高まっているため、フロントエンド開発者は、より効率的で柔軟なツールを使用して、応答性の高いインタラクティブなインターフェイスを作成する必要があります。フロントエンド開発の分野における 2 つの重要なテクノロジーである PHP と Vue.js は、組み合わせることで完璧なツールと見なされます。この記事では、PHP と Vue の組み合わせと、読者がこれら 2 つをよりよく理解し、適用できるようにするための詳細なコード例について説明します。

PHP アロー関数を使用して関数のカリー化を実装する方法 PHP アロー関数を使用して関数のカリー化を実装する方法 Sep 13, 2023 am 11:12 AM

PHP アロー関数を使用して関数のカリー化を実装する方法 カリー化 (カリー化) は関数プログラミングの概念であり、複数パラメーターの関数を 1 つのパラメーターのみを受け入れる関数シーケンスに変換するプロセスを指します。 PHP では、アロー関数を使用して関数のカリー化を実装し、コードをより簡潔かつ柔軟にすることができます。いわゆるアロー関数は、PHP7.4 で導入された新しい匿名関数構文です。外部変数を取り込めることと、関数本体となる式が1つしかないことが特徴です。

フロントエンドの面接官からよく聞かれる質問 フロントエンドの面接官からよく聞かれる質問 Mar 19, 2024 pm 02:24 PM

フロントエンド開発のインタビューでは、HTML/CSS の基本、JavaScript の基本、フレームワークとライブラリ、プロジェクトの経験、アルゴリズムとデータ構造、パフォーマンスの最適化、クロスドメイン リクエスト、フロントエンド エンジニアリング、デザインパターン、新しいテクノロジーとトレンド。面接官の質問は、候補者の技術スキル、プロジェクトの経験、業界のトレンドの理解を評価するように設計されています。したがって、候補者はこれらの分野で自分の能力と専門知識を証明するために十分な準備をしておく必要があります。

C# 開発経験の共有: フロントエンドとバックエンドの共同開発スキル C# 開発経験の共有: フロントエンドとバックエンドの共同開発スキル Nov 23, 2023 am 10:13 AM

C# 開発者としての私たちの開発作業には、通常、フロントエンドとバックエンドの開発が含まれますが、テクノロジーが発展し、プロジェクトが複雑になるにつれて、フロントエンドとバックエンドの共同開発はますます重要かつ複雑になってきています。この記事では、C# 開発者が開発作業をより効率的に完了できるようにする、フロントエンドとバックエンドの共同開発テクニックをいくつか紹介します。インターフェイスの仕様を決定した後、フロントエンドとバックエンドの共同開発は API インターフェイスの相互作用から切り離せません。フロントエンドとバックエンドの共同開発をスムーズに進めるためには、適切なインターフェース仕様を定義することが最も重要です。インターフェイスの仕様にはインターフェイスの名前が含まれます

Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Django はフロントエンドですか、バックエンドですか?それをチェックしてください! Jan 19, 2024 am 08:37 AM

Django は、迅速な開発とクリーンなメソッドを重視した Python で書かれた Web アプリケーション フレームワークです。 Django は Web フレームワークですが、Django がフロントエンドなのかバックエンドなのかという質問に答えるには、フロントエンドとバックエンドの概念を深く理解する必要があります。フロントエンドはユーザーが直接対話するインターフェイスを指し、バックエンドはサーバー側プログラムを指し、HTTP プロトコルを通じてデータと対話します。フロントエンドとバックエンドが分離されている場合、フロントエンドとバックエンドのプログラムをそれぞれ独立して開発して、ビジネス ロジックとインタラクティブ効果、およびデータ交換を実装できます。

Go 言語のフロントエンド テクノロジーの探求: フロントエンド開発の新しいビジョン Go 言語のフロントエンド テクノロジーの探求: フロントエンド開発の新しいビジョン Mar 28, 2024 pm 01:06 PM

Go 言語は、高速で効率的なプログラミング言語として、バックエンド開発の分野で広く普及しています。ただし、Go 言語をフロントエンド開発と結びつける人はほとんどいません。実際、フロントエンド開発に Go 言語を使用すると、効率が向上するだけでなく、開発者に新たな視野をもたらすことができます。この記事では、フロントエンド開発に Go 言語を使用する可能性を探り、読者がこの分野をよりよく理解できるように具体的なコード例を示します。従来のフロントエンド開発では、ユーザー インターフェイスの構築に JavaScript、HTML、CSS がよく使用されます。

フロントエンドにインスタントメッセージングを実装する方法 フロントエンドにインスタントメッセージングを実装する方法 Oct 09, 2023 pm 02:47 PM

インスタント メッセージングを実装する方法には、WebSocket、ロング ポーリング、サーバー送信イベント、WebRTC などが含まれます。詳細な紹介: 1. クライアントとサーバーの間に永続的な接続を確立してリアルタイムの双方向通信を実現できる WebSocket フロントエンドは WebSocket API を使用して WebSocket 接続を作成し、送受信によるインスタント メッセージングを実現できます。 2. Long Polling(リアルタイム通信を模擬する技術)など

Django: フロントエンド開発とバックエンド開発の両方を処理できる魔法のフレームワークです。 Django: フロントエンド開発とバックエンド開発の両方を処理できる魔法のフレームワークです。 Jan 19, 2024 am 08:52 AM

Django: フロントエンド開発とバックエンド開発の両方を処理できる魔法のフレームワークです。 Django は、効率的でスケーラブルな Web アプリケーション フレームワークです。 MVCやMTVなど複数のWeb開発モデルをサポートし、高品質なWebアプリケーションを簡単に開発できます。 Django はバックエンド開発をサポートするだけでなく、フロントエンド インターフェイスを迅速に構築し、テンプレート言語を通じて柔軟なビュー表示を実現します。 Django はフロントエンド開発とバックエンド開発をシームレスに統合するため、開発者は学習に特化する必要がありません。

See all articles