在計算機科學中,柯里化(英語:Currying),又譯爲卡瑞化或加里化,是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。javascript
Currying 的重要意義在於能夠把函數徹底變成「接受一個參數;返回一個值」的固定形式,這樣對於討論和優化會更加方便。java
將關注的重點聚焦到函數自己,而不因冗餘的數據參數分散注意力。算法
有這樣的說法,並不是柯里化有什麼意義,而是,當函數能夠做爲函數的參數和返回值,成爲函數式編程語言後,就會不可避免地產生函數柯里化。編程
先來一個簡單的 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);
}
}
}
複製代碼
函數柯里化的好處有幾個:
函數柯里化容許和鼓勵你分隔複雜功能變成更小更容易分析的部分。這些小的邏輯單元顯然是更容易理解和測試的,而後你的應用就會變成乾淨而整潔的組合,由一些小單元組成的組合。
文章開篇的 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);
});
};
}
})();
複製代碼
函數柯里化是「函數是一等公民」的編程語言環境造成的編程風格,利用了函數能做爲參數一級返回值以及利用了閉包保存變量的特色,是將多個參數的函數轉換爲接收一個參數,最後返回結果的技術。
歡迎關注個人我的公衆號「謝南波」,專一分享原創文章。