面试题:实现 add(1)(2)(3) // 结果 = 6,题的核心就是问的 js 的柯里化
先说说什么是柯里化,看过许多关于柯里化的文章,始终搞不太清楚,例如:柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数 (最初函数的第一个参数) 的函数,并且返回接受余下的参数且返回结果的新函数的技术。大多数的 blog 都是这种说法,说实话我是懵逼的。我的理解是,curry 是一个收集参数的方法,收集够了去执行函数。
实现前我们先列一下要点 1、收集参数(就像面试题多次执行多个参数)是的利用闭包 2、每次执行参数有多有少例如 add(1)(2,3)(4)3、我们需要知道什么时候参数够了
// 如题
//add(1)(2)(3)
// 逻辑应该是这样 add(1)执行收集参数 1 继续执行收集参数 2 依次类推直到收集完毕。
function curry(fn) {
let arg = []; // 用于收集参数
// 做一个闭包 https://segmentfault.com/a/1190000017824877
return function() {
// 每执行一次收集一次参数, 为什么用 concat 是因为有时候后是多个参数(2,3)
arg = arg.concat([…arguments]);
// 直到参数收集完成执行 fn
// 我们需要知道什么时候收集完了,条件就是 curry 参数 fn 的参数个数 fn.length
// 如果收集的参数个数大于等于 fn 的参数个数执行 fn, 如果没有递归执行
if (arg.length >= fn.length) {
return fn(…arg)
}
// 参数没有收集完我们需要继续收集,递归
return arguments.callee
}
}
// 测试一下
let testAdd = curry(add1)
// console.log(testAdd(1)(2)(3))
// console.log(testAdd(1, 2)(3))
//console.log(testAdd(1)(2, 3))
一不小心写完了!
不过不能标题党,说好的详解, 接下来我们解析一下网上大多数柯里化的实现代码
function curry(fn) {
function _c(restNum, argsList) {
return restNum === 0 ?
fn.apply(null, argsList) :
function(x) {
return _c(restNum – 1, argsList.concat(x));
};
}
return _c(fn.length, []);
}
一眼看不明白没事,我们多看几眼。解析:1、curry 也是接收一个参数(fn)这个是必然 2、返回了一个函数,接收两个参数,fn.length 和一个空数组这个好解释,我写的简版也说过了,fn.length 是为了判断参数是否收集够了,参数传一个空数组其实也是闭包的一种实现,用来收集参数。3、里边是一个三目判断,看着花里胡哨的没那么复杂,判断 fn 的参数个数如果是 0,那就没必要收集了直接执行 fn,至于 fn.apply(null,argList)我很明白的大声说出来会用个 apply 就到处用吗?在我看来没有一分钱用,之所以用是因为 argList 是一个数组,正好 apply 正好支持第二个参数是数组,主要看起来很牛逼的样子。4、收集参数,fn 参数个数不为零,每次收集 fn 函数参数的个数减一,直到等于 0 执行 fn,这个就没有我写的通用了,我一次传俩就挂了。
再来一个例子:bind 方法实现
Function.prototype.bind = function(context) {
// 返回一个绑定 this 的函数,我们需要在此保存 this
let self = this
// 可以支持柯里化传参,保存参数
let arg = […arguments].slice(1)
// 返回一个函数
return function() {
// 同样因为支持柯里化形式传参我们需要再次获取存储参数
let newArg = […arguments]
console.log(newArg)
// 返回函数绑定 this,传入两次保存的参数
// 考虑返回函数有返回值做了 return
return self.apply(context, arg.concat(newArg))
}
}
// 搞定测试
let fn = Person.say.bind(Person1)
fn()
fn(18)
是的 bind 方法就是用的柯里化,bind 实现详情请移步:https://segmentfault.com/a/11…