本篇目录:

  • 柯里化定义
  • 一个柯里化函数的实现
    • 实现方式
      • sum(1,2,3,4)
      • sum(1,2,3)(4)
      • sum(1,2)(3,4)
      • 个人优化
  • 小结

本篇内容主要参考了以下文章:
从 sum(2)(3) == sum(2, 3) 到实现柯里化函数
JavaScript专题之函数柯里化

柯里化定义

在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

一个柯里化函数的实现

在上面两篇文章中,两位作者都比较详细的分析了柯里化函数的实现方式,特别是冴羽的文章中给出了详细的从零开始实现柯里化函数的方法,在这里我就不再赘述了。

在这里我主要针对上面第一个文章中的实现方法,进行一下执行过程的分析。主要是针对其中的具体的一些执行过程单独看代码还是不甚明了,特此详细执行分析一下以作理解,加深记忆。

实现方式

这个方式是我感觉最容易接受的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function curry(fn, argLen, currArgs) {
return function() {
console.log("arguments:", arguments, arguments.length)
var args = [].slice.call(arguments);
console.log("args:", args)
// 首次调用时未提供最后一个参数
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 递归出口
if (args.length == argLen) {
return fn.apply(this, args);
} else {
return curry(fn, argLen, args);
}
}
}

function sumOf(a, b, c, d) {
return a + b + c + d;
}

// 改造普通函数,返回柯里函数
var sum = curry(sumOf, 4);

下面我们分别来看一下几种不同的传参方式的执行过程是怎么样的

sum(1,2,3,4)

1
2
3
>sum(1,2,3,4)
arguments: Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] 4
args: (4) [1, 2, 3, 4]

sum(1,2,3)(4)

1
2
3
4
5
>sum(1,2,3)(4)
arguments: Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ] 3
args: (3) [1, 2, 3]
arguments: Arguments [4, callee: ƒ, Symbol(Symbol.iterator): ƒ] 1
args: [4]

sum(1,2)(3,4)

1
2
3
4
5
>sum(1,2)(3,4)
arguments: Arguments(2) [1, 2, callee: ƒ, Symbol(Symbol.iterator): ƒ] 2
args: (2) [1, 2]
arguments: Arguments(2) [3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ] 2
args: (2) [3, 4]

通过上面针对几种传参方式的执行过程的分析,我们可以清楚地看出其中一些关键变量的内容,这样就让我们能够清楚地理解了柯里化函数的执行过程

个人优化

针对上面的执行方式,我们可以看出除了需要被柯里化的函数外,还需要根据这个函数的参数数量传入对应的数量值,这个并不是那么的方便。不妨改进一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function curry(fn, currArgs) {
return function() {
console.log("arguments:", arguments, arguments.length)
var args = [].slice.call(arguments);
console.log("args:", args)
// 首次调用时未提供最后一个参数
if (currArgs !== undefined) {
args = args.concat(currArgs);
}
// 递归出口
if (args.length == fn.length) {
return fn.apply(this, args);
} else {
return curry(fn, args);
}
}
}

这里我们通过利用length 属性指明函数的形参个数,来获取被柯里化函数的参数数量,这样就不需要我们在额外传入了。但是此时需要注意的是,被柯里化的函数的参数不能有默认值,不然的话,length属性就没有意义了。所以这个方式也不是完美的。

小结

简单来说,实现柯里化函数的方式基本都会需要根据参数以及递归方式来实现,这样才能让我们通过拆分参数的方式来调用一个多参数的函数方法。这样的好处是减少重复传递不变的部分参数,将柯里化后的callback参数传递给map, filter等函数

1
2
3
4
5
6
7
8
9
10
var persons = [{name: 'kevin', age: 11}, {name: 'daisy', age: 24}]

let getProp = _.curry(function (key, obj) {
return obj[key]
});
let names2 = persons.map(getProp('name'))
console.log(names2); //['kevin', 'daisy']

let ages2 = persons.map(getProp('age'))
console.log(ages2); //[11,24]

但是也不得不承认,这是个非常高阶的用法,普通的开发场景中很少会被使用,毕竟理解并不是那么的简单。