#### 前言
在計算機科學中,柯里化(英語: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);
}
}
}
三、應用場景
函數柯里化的好處有幾個:
- 參數複用;
- 提前返回;
- 延遲計算/運行。
函數柯里化允許和鼓勵你分隔複雜功能變成更小更容易分析的部分。這些小的邏輯單元顯然是更容易理解和測試的,然後你的應用就會變成乾淨而整潔的組合,由一些小單元組成的組合。
文章開篇的 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);
});
};
}
})();
總結
函數柯里化是“函數是一等公民”的編程語言環境形成的編程風格,利用了函數能作爲參數一級返回值以及利用了閉包保存變量的特點,是將多個參數的函數轉換爲接收一個參數,最後返回結果的技術。
歡迎關注我的個人公衆號“謝南波”,專注分享原創文章。