JS專題之函數柯里化

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

#### 1、爲何會有函數柯里化?
Currying 的重要意義在於能夠把函數徹底變成「接受一個參數;返回一個值」的固定形式,這樣對於討論和優化會更加方便。 java

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

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

#### 2、具體實現
先來一個簡單的 add 函數瀏覽器

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

add(2, 3);  // 5

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

那麼,繼續:app

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);
        }
    }
}

3、應用場景

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

  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);
            });
        };
    }
})();

總結

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

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

相關文章
相關標籤/搜索