JS專題之函數柯里化

#### 前言
在計算機科學中,柯里化(英語:Currying),又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。

#### 一、爲什麼會有函數柯里化?
Currying 的重要意義在於可以把函數完全變成「接受一個參數;返回一個值」的固定形式,這樣對於討論和優化會更加方便。

將關注的重點聚焦到函數本身,而不因冗餘的數據參數分散注意力。

有這樣的說法,並非柯里化有什麼意義,而是,當函數可以作爲函數的參數和返回值,成爲函數式編程語言後,就會不可避免地產生函數柯里化。

#### 二、具體實現
先來一個簡單的 add 函數

function add(x, y) {
    return x + y;
}

add(2, 3);  // 5

重要的概念多說一遍:函數柯里化就是接收多個參數的函數變換爲接收一個函數,並返回接收餘下參數,最終能返回結果的技術

那麼,繼續:

function add(x) {
    return function(y) {
        return x + y;
    }
}

add(2)(3);  // 5

所以,曾經的一個函數,因爲閉包操作(返回函數並訪問了自由變量的行爲),變成了多個接收一個參數的函數。

所以簡單來講:函數柯里化就是意圖將函數的參數變成一個。讓函數可以輸入一個值,就返回一個相對應的值,從而實現純函數化。

爲什麼函數式編程要求函數必須是純的,不能有副作用?因爲它是一種數學運算,原始目的就是求值,不做其他事情,否則就無法滿足函數運算法則了。在函數式編程中,函數就是一個管道(pipe)。這頭進去一個值,那頭就會出來一個新的值,沒有其他作用。

所以良好的編程規範是儘可能讓函數塊做一個事情,實現可複用性,可維護性。

上面的例子中,如果有很多個參數怎麼辦,難道一層層嵌套?

我們繼續:

function plus(value) {
    "use strict";
    var add = function () {
        var args = [];
        var adder = function adder() {
            Array.prototype.push.apply(args,Array.prototype.slice.apply(arguments))
            return adder;
        }
        adder.toString = function () {
            return args.reduce(function(a, b) {
                return a + b;
            })
        }
        return adder;
    }
    return add()(value);
}

plus(2)(3)(5).toString();  // 10;

上面的代碼看起來不那麼優雅,如果是減法,我們就得又重新爲減法寫這麼多的代碼。像 lodash, underscore 這些工具庫,都提供了柯里化的工具函數。

我們一起來試着實現:

function curry(fn, args) {
    var length = fn.length;  // 函數參數的長度

    // 閉包保存參數列表
    args = args || [];

    return function() {
        // 獲取參數列表。
        var _args = args.slice(0);
        
            Array.prototype.push.apply(_args, Array.prototype.slice.call(arguments))

        if (_args.length < length) {
        // 如果傳入的參數列表長度還沒有超過函數定義時的參數長度,就 push 新的參數到參數列表中保存起來。
        
            // 自己調用自己,將保存的參數傳遞到下一個柯里化函數。
            return curry.call(this, fn, _args);
        }
        else {
        // 如果傳入的參數列表長度已經超過函數定義時的參數長度,就執行。
            return fn.apply(this, _args);
        }
    }
}

三、應用場景

函數柯里化的好處有幾個:

  1. 參數複用;
  2. 提前返回;
  3. 延遲計算/運行。

函數柯里化允許和鼓勵你分隔複雜功能變成更小更容易分析的部分。這些小的邏輯單元顯然是更容易理解和測試的,然後你的應用就會變成乾淨而整潔的組合,由一些小單元組成的組合。

文章開篇的 add 函數,假如,每次調用加法有一個初始值會怎樣?

var add = curry(function(a, b, c) {
    return a + b + c;
})

var addTen = add(10);

var addSix = add(6);

addTen(2)(3);  // 15;

addSix(7)(8);  // 21;

以上代碼就實現了參數複用,保存固定參數的函數。

看一個經典的例子:
元素綁定事件監聽器:

var addEvent = function(el, type, fn, capture) {
    if (window.addEventListener) {
        el.addEventListener(type, function(e) {
            fn.call(el, e);
        }, capture);
    } else if (window.attachEvent) {
        el.attachEvent("on" + type, function(e) {
            fn.call(el, e);
        });
    } 
};

以上代碼是爲了兼容 IE 瀏覽器對 DOM 事件綁定做的函數封裝。

問題在於,每次對 DOM 元素進行事件綁定時,函數內部都會走一遍 if else。那麼用函數柯里化就能實現提前返回

var addEvent = (function(){
    if (window.addEventListener) {
        return function(el, sType, fn, capture) {
            el.addEventListener(sType, function(e) {
                fn.call(el, e);
            }, (capture));
        };
    } else if (window.attachEvent) {
        return function(el, sType, fn, capture) {
            el.attachEvent("on" + sType, function(e) {
                fn.call(el, e);
            });
        };
    }
})();

總結

函數柯里化是“函數是一等公民”的編程語言環境形成的編程風格,利用了函數能作爲參數一級返回值以及利用了閉包保存變量的特點,是將多個參數的函數轉換爲接收一個參數,最後返回結果的技術。

歡迎關注我的個人公衆號“謝南波”,專注分享原創文章。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章