前言 本文1454字,閱讀大約需要4分鐘。 總括: 本文以初學者的角度來闡述Javascript中柯里化的概念以及如何在工作中進行使用。 原文地址: "理解Javascript的柯里化" 知乎專欄: "前端進擊者" 博主博客地址: "Damonare的個人博客" 事親以敬,美過三牲。 正文 函數式編 ...
前言
本文1454字,閱讀大約需要4分鐘。
總括: 本文以初學者的角度來闡述Javascript中柯里化的概念以及如何在工作中進行使用。
原文地址:理解Javascript的柯里化
知乎專欄: 前端進擊者
博主博客地址:Damonare的個人博客
事親以敬,美過三牲。
正文
函數式編程是一種如今比較流行的編程範式,它主張將函數作為參數進行傳遞,然後返回一個沒有副作用的函數,說白了,就是希望一個函數只做一件事情。
像Javascript,Haskell,Clojure等編程語言都支持函數式編程。
這種編程思想涵蓋了三個重要的概念:
- 純函數
- 柯里化
- 高階函數
而這篇文章主要是想向大家講清楚柯里化
這個概念。
什麼是柯里化
首先我們先來看一個例子:
function sum(a, b, c) {
return a + b + c;
}
// 調用
sum(1, 2, 3); // 6
上述函數實現的是將a,b,c三個參數相加,改寫為柯里化函數如下:
function sum(a) {
return function (b) {
return function(c) {
return a + b + c;
}
}
}
// 調用
let sum1 = sum(1);
let sum2 = sum1(2);
sum2(3); // 6
所謂柯里化就是把具有較多參數的函數轉換成具有較少參數的函數的過程。
我們來一步步看上面那個柯里化函數做了什麼,首先第一步調用了sum(1),此時變數sum1相當於:
sum1 = function(b) {
return function(c) {
// 註意此時變數a存在於閉包中,可以調用,a = 1
return a + b + c;
}
}
然後調用sum1(2),此時賦值給變數sum2相當於:
sum2 = function(c) {
// 變數a,b皆在閉包中, a = 1, b = 2
return a + b + c;
}
最後調用sum2(3),返回1 + 2 + 3的結果6;
這就是一個最簡單的柯里化函數,是不是很簡單呢?
柯里化函數的作用
那麼問題來了,上面改寫後的柯里化函數和原函數比起來代碼多了不少,而且也不如原函數好理解,柯里化函數到底有什麼用呢?
確實,柯里化函數在這裡看起來的確是很臃腫,不實用,但在很多場景下他的作用是很大的,甚至很多人在不經意間已經在使用柯里化函數了。舉一個簡單的例子:
假設我們有一批的長方體,我們需要計算這些長方體的體積,實現一個如下函數:
function volume(length, width, height) {
return length * width * height;
}
volume(200, 100, 200);
volume(200, 150, 100);
volume(200, 50, 80);
volume(100, 50, 60);
如上計算長方體的體積函數會發現存在很多相同長度的長方體,我們再用柯里化函數實現一下:
function volume(length, width, height) {
return function(width) {
return function(height) {
return length * width * height;
}
}
}
let len200 = volume(200);
len200(100)(200);
len200(150)(100);
len200(50)(80);
volume(100)(50)(60);
如上,通過實現一個len200函數我們統一處理長度為200的長方體的體積,這就實現了參數復用。
我們再舉一個只執行一次函數的例子:
function execOnce(fun) {
let flag = true;
return function() {
if (flag) {
fun && fun();
flag = false;
}
}
}
let onceConsole = execOnce(function() {
console.log('只列印一次');
});
onceConsole();
onceConsole();
如上,我們實現了一個execOnce函數,該函數接受一個函數參數,然後返回一個函數,變數flag存在閉包中,用來判斷返回的函數是否執行過,onceConsole相當於:
let onceConsole = function() {
if (flag) {
(function() {
console.log('只列印一次');
})()
flag = false;
}
}
這也是柯里化函數的一個簡單應用。
通用柯里化函數的實現
既然柯里化函數這麼實用,那麼我們能不能實現一個通用的柯里化函數呢?所謂通用,就是說該函數可以把函數參數轉換為柯里化函數,看下第一版實現的代碼:
// 第一版
var curry = function (fn) {
var args = [].slice.call(arguments, 1);
return function() {
var newArgs = args.concat([].slice.call(arguments));
return fn.apply(null, newArgs);
};
};
function add(a, b) {
return a + b;
}
var addFun = curry(add, 1, 2);
addFun() // 3
//或者
var addOne = curry(add, 1);
如上代碼,我們接受一個函數作為參數,然後收集其它的參數,將這些參數傳給這個函數參數去執行。但上面的代碼有個問題,參數不夠自由,比如我們想這麼調用就會報錯:
var addFun = curry(function(a, b,c) {
return a + b + c;
}, 1);
addFun(2)(3); // 報錯 addFun(...) is not a function
這好像違背了我們參數復用的原則,改進如下:
function curry(fn, args) {
var length = fn.length;
args = args || [];
return function(...rest) {
var _args = [...args, ...rest];
return _args.length < length
? curry.call(this, fn, _args)
: fn.apply(this, _args);
}
}
var fn = curry(function(a, b, c) {
console.log(a + b + c);
});
fn('a', 'b', 'c'); // abc
fn('a', 'b')('c'); // abc
fn('a')('b')('c'); // abc
如上實現就很完善,該工具函數的實現總結起來就一句話:
利用閉包將函數的參數儲存起來,等參數達到一定數量時執行函數。
後記
柯里化是以閉包為基礎的,不理解閉包可能對柯里化的理解有所阻礙,希望通過這篇文章能讓各位瞭解和理解Javascript的柯里化。
能力有限,水平一般,歡迎勘誤,不勝感激。
訂閱更多文章可關註「菜鳥學前端」,回覆「666」,獲取一攬子前端技術書籍
- 回覆「666」,可領取一攬子前端技術書籍;