总结要点:
- 柯里化实现只需要2层嵌套,内层只需要是匿名函数。内层函数分类讨论,处理递归调用和运行
f 这两种情况。 - 柯里化实现用到了
f.length ,是为了知道,当前收集的参数个数够不够f 接收的参数个数。
柯里化是一种转换,将 f(a,b,c) 转换为可以被以 f(a)(b)(c) 的形式进行调用。JavaScript实现通常都保持该函数可以被正常调用,并且如果参数数量不足,则返回偏函数。
既然柯里化能知道参数数量是否足够,那么显然它只允许确定参数长度的函数,再强调一遍:
柯里化要求函数具有固定数量的参数。使用 rest 参数的函数,例如 f(...args) ,不能以这种方式进行柯里化。
柯里化实现
首先,function有name和length这两种常用属性
function f(a,b,...args){}
根据这个length属性,我们只需要在函数套函数套函数(3层嵌套,因为我们要在下一层括号括起来之后再运行递归调用的curry函数)递归收集参数(用concat),直到参数个数达到f.length ,运行函数并结束递归。其中f 是传进来的函数。
function curryIt(f,context){
return function curry(){
if(arguments.length >= f.length){
return f.apply(context,arguments)
}
let args = [].slice.call(arguments)
return function(){
return curry.apply(context,args.concat([].slice.call(arguments)))
}
}
}
let sum = curryIt((a,b,c,d,e) => a + b + c + d + e)
console.log(sum(1,2)(3)(4,5))
console.log(sum(10,20)(30,40)(50))
console.log(sum(100,200,300,400,500))
let o = {
name: 'wsw',
f: function(x,y,z){console.log(this.name,x,y,z)}
}
let o1 = {name: 'hans'}
let oF = curryIt(o.f,o)
console.log(oF(11)(45)(14))
oF = curryIt(o.f,o1)
console.log(oF(114)(514)(0))
把它放到原型链里
Function.prototype.curryIt = function(context){
if(typeof this !== 'function') throw new Error('应该是函数调用!')
let self = this
return function curry(){
if(self.length <= arguments.length){
return self.apply(context,arguments)
}
let args = [].slice.call(arguments)
return function(){
return curry.apply(context,args.concat([].slice.call(arguments)))
}
}
}
let sum = ((a,b,c,d,e) => a + b + c + d + e).curryIt()
console.log(sum(1,2)(3)(4,5))
console.log(sum(10,20)(30,40)(50))
console.log(sum(100,200,300,400,500))
let o = {
name: 'wsw',
f: function(x,y,z){
console.log(this.name,x,y,z)
return this
}
}
let o1 = {name: 'hans'}
let oF = o.f.curryIt(o)
console.log(oF(11)(45)(14))
oF = o.f.curryIt(o1)
console.log(oF(114)(514)(0))
但总觉着3层嵌套的函数非常抽象,能不能只嵌套2层?当然可以!
- 递归的不再是内层定义的curry函数,而是外层的curryIt函数。
- 为了不再使用
[].slice.call(arguments,1) 这种冗长的写法,我们规定了args是一个参数数组,且默认值为空数组。 - 这种写法允许我们像apply一样预先接收一些参数了。
Function.prototype.curryIt = function(context = null,args = []){
let f = this
if(typeof this !== 'function') throw new Error('应该是函数调用!')
return function(){
let all = args.concat([].slice.call(arguments))
if(all.length >= f.length){
return f.apply(context,all)
}
return Function.prototype.curryIt.call(f,context,all)
}
}
let sum = ((a,b,c,d,e) => a + b + c + d + e).curryIt()
console.log(sum(1,2)(3)(4,5))
console.log(sum(10,20)(30,40)(50))
console.log(sum(100,200,300,400,500))
let o = {
name: 'wsw',
f: function(x,y,z){
console.log(this.name,x,y,z)
return this
}
}
let o1 = {name: 'hans'}
let oF = o.f.curryIt(o)
console.log(oF(11)(45)(14))
oF = o.f.curryIt(o1)
console.log(oF(114)(514)(110))
oF = o.f.curryIt(o1,[114])
console.log(oF(5)(14))
oF = o.f.curryIt(o1,[19])
console.log(oF(19)(810))
小插曲:一开始let all = args.concat([].slice.call(arguments)) 写的是args = args.concat([].slice.call(arguments)) ,于是发现测试代码里,第二次调用sum的时候报错。后来才发现args在函数运行完后应该保持原貌!h鶸不会最基本的递归不会最基本的遍历数组又一石锤。呜呜呜~
我们有一个不够完备的手打bind,它的思想和柯里化类似,也是外层函数参数拼接内层函数参数,但理解起来更简单。简单的原因,也许是它只有”运行“这一种情况,但柯里化还多了”递归“这种情况。并且同样能实现以上test里的功能。
Function.prototype._bind = function (context) {
let self = this;
if(typeof this !== 'function') throw new Error('应该是函数调用!')
let firstArg = [].slice.call(arguments,1);
return function () {
let secArg = [].slice.call(arguments);
let finishArg = firstArg.concat(secArg);
return self.apply(context,finishArg);
}
}
function sum(){return [].reduce.call(arguments,(a,b) => a+b,0)}
let f = sum._bind(null,10,20,30)
let g = f._bind(null,40,50)
console.log(g())
let g1 = g._bind(null,60)
console.log(g1())
let h = f._bind(null,60)
console.log(h())
参考:https://zh.javascript.info/currying-partials
|