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);
            });
        };
    }
})();
複製代碼

總結

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

掘金專欄 JavaScript 系列文章

  1. JavaScript之變量及做用域
  2. JavaScript之聲明提高
  3. JavaScript之執行上下文
  4. JavaScript之變量對象
  5. JavaScript之原型與原型鏈
  6. JavaScript之做用域鏈
  7. JavaScript之閉包
  8. JavaScript之this
  9. JavaScript之arguments
  10. JavaScript之按值傳遞
  11. JavaScript之例題中完全理解this
  12. JavaScript專題之模擬實現call和apply
  13. JavaScript專題之模擬實現bind
  14. JavaScript專題之模擬實現new
  15. JS專題之事件模型
  16. JS專題之事件循環
  17. JS專題之去抖函數
  18. JS專題之節流函數

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

相關文章
相關標籤/搜索