搞懂Javascript柯里化概念

1、 前言

之前一直听说过柯里化不过一直没用过,最近在看闭包时提到闭包的一个用途之一就是柯里化。正好来了解一下。

1.1 铺垫知识

虽然在实际项目中没见过这种写法fn(a)(b)(c)(也有可能是我太菜没机会用到…),但是搜索柯里化相关的文章时,到处都是这种写法。
在看到这篇文章才知道,如果一个函数的返回值是另外一个函数,就可以用两个括号调用了

1
2
3
4
5
6
7
8
// 举个🌰:
function fn(m) {
return function (n) {
return m + '与' + n + '前端学习';
}
}
let result = fn('小明')('小红');
console.log(result); // 小明与小红前端学习

2、什么是柯里化 & 怎么实现柯里化

2.1 柯里化的概念

柯里化(Currying),又称部分求值(Partial Evaluation),是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

文字看起来比较晦涩难懂,来一段代码简单粗暴的解释一下:

1
2
3
4
5
6
7
8
9
function add(a, b, c) {
return a + b + c;
}

// 正常调用方式
add(1,2,3)

// 柯里化之后的版本调用方式
curryAdd(1)(2)(3)

2.2 怎么实现柯里化

2.2.1 最简单的柯里化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 原函数
function add(a, b, c) {
return a + b + c;
}

// 柯里化函数
function addCurrying(a) {
return function (b) {
return function (c) {
return a + b + c;
}
}
}

// 调用原函数
add(1, 2, 3); // 6

// 调用柯里化函数
addCurrying(1)(2)(3) // 6

被柯里化的函数 addCurrying 每次的返回值都为一个函数,并使用下一个参数作为形参,直到三个参数都被传入后,返回的最后一个函数内部执行求和操作,其实是充分的利用了闭包的特性来实现的。

2.2.2 封装通用的柯里化函数

上面的🌰太过死板,不具有通用性。我们需要再次封装一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function currying(func, args) {
// 形参个数
var arity = func.length;
// 上一次传入的参数
var args = args || [];
return function () {
// [].slice.call(arguments)能将具有length属性的对象转成数组,此处是将参数转化为数组
var _args = [].slice.call(arguments);
// 将上次的参数与当前参数进行组合并修正传参顺序
Array.prototype.unshift.apply(_args, args);
// 如果参数不够,返回闭包函数继续收集参数
if (_args.length < arity) {
return currying.call(null, func, _args);
}
// 参数够了则直接执行被转化的函数
return func.apply(null, _args);
}
}
function add(a, b, c) {
return a + b + c;
}

// 调用
const aaa = currying(add);
aaa(1)(2)(3) // 输出 6

上面的例子用了callapply来改变this指向,代码有些冗余,下面用es6来重写一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function currying(func, args = []) {
let arity = func.length;
return function (..._args) {
_args.unshift(...args);
if (_args.length < arity) {
return currying(func, _args);
}
return func(..._args);
}
}
function add(a, b, c) {
return a + b + c;
}
const aaa = currying(add);
aaa(1)(2)(3) // 输出 6

3、 使用场景

3.1 参数复用

举个例子,你有一个商店🏠,你想给你的顾客 10% 的折扣:

1
2
3
function discount(price, discount) {
return price * discount
}

当一个客户买了一件价值$500的商品,你会给他:

1
const price = discount(500, 0.10); // $50 

如果有其他客户分别买了价值$50,$60,$70的商品,那么则需要多次计算

1
2
3
const price = discount(50, 0.10); // $5
const price = discount(60, 0.10); // $6
const price = discount(700, 0.10); // $7

柯里化一下这个折扣函数

1
2
3
4
5
6
7
function curryDiscount(discount) {
return function (price) {
return price * 0.1;
}
}
const tenPercentDiscount = curryDiscount(0.1);
// 接下来计算折扣只需要 tenPercentDiscount(price)

3.2 延迟计算

// 暂时没找到示例

3.3 提前返回

// 暂时没找到示例

参考资料:
高阶函数应用 —— 柯里化与反柯里化
大佬,JavaScript 柯里化,了解一下?
JS中的柯里化(currying)
「译」理解JavaScript的柯里化
简述几个非常有用的柯里化函数使用场景


搞懂Javascript柯里化概念
https://xypecho.github.io/2019/12/03/搞懂Javascript柯里化概念/
作者
很青的青蛙
发布于
2019年12月3日
许可协议