前言
在電腦科學中,柯里化(英語: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);
});
};
}
})();
複製程式碼
總結
函式柯里化是“函式是一等公民”的程式語言環境形成的程式設計風格,利用了函式能作為引數一級返回值以及利用了閉包儲存變數的特點,是將多個引數的函式轉換為接收一個引數,最後返回結果的技術。
掘金專欄 JavaScript 系列文章
- JavaScript之變數及作用域
- JavaScript之宣告提升
- JavaScript之執行上下文
- JavaScript之變數物件
- JavaScript之原型與原型鏈
- JavaScript之作用域鏈
- JavaScript之閉包
- JavaScript之this
- JavaScript之arguments
- JavaScript之按值傳遞
- JavaScript之例題中徹底理解this
- JavaScript專題之模擬實現call和apply
- JavaScript專題之模擬實現bind
- JavaScript專題之模擬實現new
- JS專題之事件模型
- JS專題之事件迴圈
- JS專題之去抖函式
- JS專題之節流函式
歡迎關注我的個人公眾號“謝南波”,專注分享原創文章。