目录
准备前端面试的朋友们一定不会对这道题目陌生:
-
写一个sum函数,让sum(2)(3)返回5
相信很多人都可以很轻松地写出代码。但是背后的知识你真的了解么?
1.问题解决
首先,我们还是先解决上面的问题吧。
相信很多人都听说过,“在JavaScript中,函数一等公民(functions as first-class citizens)”这个说法。这就意味着,函数可以作为别的函数的参数、函数的返回值、传给另一个变量等等。
在sum(2)(3)中,想要xxx(3)能执行,相比sum(2)一定是一个函数。函数执行之后还是一个函数,顺着这个思路,很容易想到解法:
function sum(a) {
return function (b) {
return a + b;
}
}
这就是柯里化!
2.什么是柯里化?
柯里化是把接受多个参数的函数变换成接受一个单一参数的函数,并且返回接受余下参数的新函数的技术。
比如说,sum(2, 3)可以转换成sum(2)(3)。
3.柯里化有什么用
有的人会问,我直接用sum(2, 3)不行么,为什么要“费力不讨好”地转换成sum(2)(3)?
柯里化的好处在于,调用函数的时候,如果某一个参数在每次调用中都相同,可以避免重复传入这个参数。
举个简单的例子吧:有一个函数叫做isSameUser(user1, user2),这是用来检测user1和user2是不是相同的user。
但是在某个文件之中,我们只想检测user2是否是loggedInUser,那么我们就可以固定一个参数:
var isLoggedInUser = curry(isSameUser);
然后只需要调用isLoggedInUser(user2)就可以了。
例子比较简单,实际的工作中遇到更加复杂的情形,使用柯里化可以避免不停地传入相同的参数。
4.柯里化函数
红宝书上给了我们一个柯里化函数实现的方法:
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
}
}
这个实现方法中,调用curry的时候,传入的第一个参数是你想柯里化的函数,比如sum,之后的参数是sum这个函数的参数,记为args。在返回函数中,再传入剩下的参数,记为innerArgs,两者拼接到一起成为finalArgs,最后使用fn调用。
使用方法可以是这样:
function sum(a, b) {
return a + b;
}
// 刚开始不传入参数,之后一次传入
var currySum = curry(sum);
console.log(currySum(2, 3));
// 刚开始传入一个参数,之后传入剩下参数
var currySum = curry(sum, 2);
console.log(currySum(3));
// 刚开始传入所有参数,之后不传参数
var currySum = curry(sum, 2, 3);
console.log(currySum());
不过这个方法只可以把参数列表分成两次传递进去。如果遇到sum(2, 3, 4)转为sum(2)(3)(4),或者更复杂的情形就无能为力。
那么有没有一种实现方法可以自动检测参数是否传递完毕,然后再执行呢?看下面这段代码:
function sum(a, b, c) {
return a + b + c;
}
function curry(fn) {
return function currify() {
const args = Array.prototype.slice.call(arguments);
return args.length >= fn.length ?
fn.apply(null, args) :
currify.bind(null, ...args);
}
}
var currySum = curry(sum);
在curry这个函数当中,返回一个叫做currify的函数,之所以不能直接使用匿名函数,是因为我们还需要在内部递归调用currify。之后获取参数存在args中。关键的步骤来了:如果args的长度大于fn.length(函数的length就是函数期望传入参数的长度),这说明已经传入足够的参数,就可以使用fn.apply执行fn;但是,如果传入的参数个数不足,那么就会返回currify.bind(null, ...args);把当前的参数列表传给currify这个函数。(如果对bind不理解可以阅读我的《前端面试题——自己实现bind》)。
大功告成,我们来测试一下把:
console.log(currySum(1)(2)(3));
console.log(currySum(1, 2)(3));
console.log(currySum(1)(2, 3));
以上三个输出都应该是6。