js函数式编程——柯里化函数

一、相关回顾

1、.length的用法

​ 函数中我们可以使用函数名.length来查询函数的参数的个数,这个属性的值永远不会改变,并且总是匹配函数声明参数的数量。

1
2
3
4
function test(a,b,c){
console.log(test.length) //3
}
test(1,2,3)

2.函数的arguments

​ 在函数域内都有一个arguments 变量来访问传递给函数的所有参数。这是一个类数组,类数组有数组的length用法,并且可以用下标的索引方式来访问每个元素。和数组不同的是,他的类型是Object,并且不能调用数组的类似pop()之类的API,也不能是使用for in语句来实现对类数组的遍历。类数组转化为数组的方式是Array.prototype.slice.call(arguments),当然可以简写为[].slice.call(arguments)

​ 在ES语法中,我们也可以使用spread operator(扩展操作) / rest parameters(剩余参数) 来访问参数:

1
2
3
function howMany(...args) {  
console.log("args:", args, ", length:", args.length);
}

二、 Currying(柯里化) 函数

​ Currying(柯里化) 函数是加上就是把接受 N 个参数的函数转换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果。并且我们也希望可以用一个或多个参数来调用它,然后它将部分应用;直到它收到最后一个参数(基于原始函数的参数数量),此时它将返回使用所有参数调用原始函数的计算值。 于是我们可以做下面的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function curry(fn){
return function curried(){
var agrs = [].slice.call(arguments)
return agrs.length >= fn.length ?
fn.apply(null,agrs) :
function() {
var rest = [].slice.call(arguments)
return curried.apply(null,agrs.concat(rest))
}
}
}

function add(a,b,c,d)
{
return a + b + c + d;
}
var curry_call = curry(add)
console.log(curry_call(1)(2,3)(4))

​ 每次调用的时候,我们都会获取调用时候的参数并且会结合之前的参数进行判断,当前已经获取的参数是否已经大于声明的函数的参数,如果大于等于就会执行被声明的函数,如果不大于就会返回一个函数继续重复接受参数并执行。

​ 但是上面的写法中,我们在每次设置apply函数的环境的时候都是null,这样在一些情况下会出问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function curry(fn){ 
return function curried(){
var agrs = [].slice.call(arguments)
return agrs.length >= fn.length ?
fn.apply(null,agrs) :
function() {
var rest = [].slice.call(arguments)
return curried.apply(null,agrs.concat(rest))
}
}
}
var people = {
name:'xiaoming',
setPeople:function(sex,height,age){
return [this.name + ':' , age , sex , height].join(' ')
}
}

people.mytest = curry(people.setPeople)

console.log(people.mytest('man')(170,20))

​ 发现执行的结果中,name变成了undefined,这就是因为我们的this在调用的时候赋了null导致的。这时候我们就需要指定该函数执行的上下文this,在我们第一次调用的时候我们就利用context纪录每次执行的环境this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function curry(fn){ 
return function curried(){
let context = this
var agrs = [].slice.call(arguments)
return agrs.length >= fn.length ?
fn.apply(context,agrs) :
function() {
var rest = [].slice.call(arguments)
return curried.apply(context,agrs.concat(rest))
}
}
}
var people = {
name:'xiaoming',
setPeople:function(sex,height,age){
return [this.name + ':' , age , sex , height].join(' ')
}
}
people.mytest = curry(people.setPeople)
console.log(people.mytest('man')(170,20))
0%