阅读本篇博客之前需要对JavaScript的闭包有一个很好的了解,可以看一下我之前写的《前端战五渣学JavaScript——闭包》
我自认为大家是在了解闭包的情况下阅读这篇博客的。
最近对什么都提不起兴趣,什么都不想干,什么都不想学,游戏也不想打。。。哎。。但是最近看了一部国产动画电影《哪吒之魔童降世》,感觉现在国产动画的水平真是越来越高了,很不错,值得一看哦
什么是函数柯里化
函数柯里化(function currying),把函数做成咖喱味的??其实我初次听说这个词的时候,感觉好高大上啊,音译,深奥,等我翻看了相关书籍(《JavaScript高级程序设计》)以及一些前辈写的博客以后,发现原来函数柯里化就是使用闭包返回一个函数
柯里化的函数可以延迟接收参数,就是比如我一个函数需要接收的参数是两个,我执行的时候必须接收两个参数,否则我没法执行啊,是不是,就容易出问题。但是柯里化后的函数,可以先接收一个函数,然后再接收一个函数,这么说太生硬了,那我们就来看一个简单的例子(也是全网最普遍的例子)⬇️
这是普通的函数
// 正常我们声明函数的时候是这样的
function 求两数之和(第一个数, 第二个数) {
return 第一个数 + 第二个数
}
const 和 = 求两数之和(1, 2);
console.log(`两数之和为 ${和}`); // 3
那我们按照柯里化写出来的函数是什么样的呢
// 理解柯里化的思想
function 求两数之和(第一个数) {
return function (第二个数) {
return 第一个数 + 第二个数
}
}
// 第一种方法
const 和 = 求两数之和(1)(2);
console.log(`第一种方法的两个数之和为 ${和}`);
// 第二种方法
const 已接收第一个数的求和函数 = 求两数之和(1);
const 两个数都已接收的和 = 已接收第一个数的求和函数(2);
console.log(`第二种方法的两个数之和为 ${两个数都已接收的和}`);
从上面的函数对比我们就可以发现,柯里化的函数可以先接收一个函数,然后在我们需要的传入第二个参数的时候,我们再传入,并执行最终的结果。
所以我们可以动态生成比如第一个参数相同,第二个参数不同,或者第二个参数相同,第一个参数不同的函数
把函数柯里化的函数
在我们熟知的《JavaScript高级程序设计》中给出了一个方法,这个方法,可以让我们把一个本来不支持柯里化的函数,转化成支持柯里化思想的函数。
function 把函数柯里化(需要柯里化的函数) {
const 调用柯里化时除了函数以外的参数 = Array.prototype.slice.call(arguments, 1);
return function () {
const 后接受的参数 = [...arguments];
const 最终的参数 = [...调用柯里化时除了函数以外的参数, ...后接受的参数];
return 需要柯里化的函数.apply(null, 最终的参数);
}
}
function 求两数之和(第一个数, 第二个数) {
return 第一个数 + 第二个数
}
const 柯里化的求两数之和 = 把函数柯里化(求两数之和, 1);
console.log(
`被柯里化后的函数求的两数之和为 ${
柯里化的求两数之和(2)
}`
); // 被柯里化后的函数求的两数之和为 3
我们发现,我们可以单独写一个函数,然后把一个普通函数转化成支持柯里化的函数,但是高级程序设计上提供的这个方法,只能提供两次调用,什么意思?我们来看看lodash
中curry
方法是如何实现的
const _ = require('lodash');
function 求三个数之和(第一个数, 第二个数, 第三个数) {
return 第一个数 + 第二个数 + 第三个数
}
const 柯里化后的求三个数之和 = _.curry(求三个数之和);
console.log(柯里化后的求三个数之和(1)(2)(3)); // 6
console.log(柯里化后的求三个数之和(1)(2, 3)); // 6
console.log(柯里化后的求三个数之和(1, 2, 3)); // 6
这下我们看到,lodash
中对转化柯里化的处理,我们可以任意调用被柯里化的函数,不管参数怎么传,只要传够三个就行,也不管是分几次传。
我们自己来一个柯里化函数吧
我们的需求是什么?
feature
- 需要把普通函数转化成柯里函数
- 只要参数没达到被转换函数的数量,就返回函数,保存已传参数
- 支持参数没达到被转换函数的数量时,无限调用
那我们先来看看我们需要转换的函数⬇️
const 封神榜 = (名字, 宝物, 出生地) => console.log(`我是${出生地}的${名字},我有${宝物}`);
封神榜('哪吒', '乾坤圈', '陈塘关'); // 我是陈塘关的哪吒,我有乾坤圈
看看我们普通转化的结果是什么样的⬇️
const 封神榜 = 名字 => 宝物 => 出生地 => console.log(`我是${出生地}的${名字},我有${宝物}`);
封神榜('哪吒')('乾坤圈')('陈塘关');
普通转化的函数,我们传参不是很自由,必须穿三次,每次传一个,那我们来个牛逼的⬇️
const 封神榜 = (名字, 宝物, 出生地) => console.log(`我是${出生地}的${名字},我有${宝物}`);
const 柯里化 = (需要柯里化的函数, ...参数) => 需要柯里化的函数.length <= 参数.length
? 需要柯里化的函数(...参数)
: (...更多参数) => 柯里化(需要柯里化的函数, ...参数, ...更多参数);
const 柯里化后的封神榜 = 柯里化(封神榜);
柯里化后的封神榜('哪吒', '乾坤圈', '陈塘关'); // 我是陈塘关的哪吒,我有乾坤圈
柯里化后的封神榜('孙悟空')('金箍鲁棒')('花果山'); // 我是花果山的孙悟空,我有金箍鲁棒
柯里化后的封神榜('雷震子', '黄金棍')('终南山玉柱洞'); // 我是终南山玉柱洞的雷震子,我有黄金棍
柯里化后的封神榜()()()()()()()()('姜子牙', '打神鞭', '昆仑山'); // 我是昆仑山的姜子牙,我有打神鞭
看,现在我们实现的柯里化函数已经基本满足我们刚才提的需求了
当然,有柯里化就有反柯里化,但是我觉得没什么必要,我既然都已经柯里化了,柯里化后的函数当然支持非柯里化函数的传参方式,但是有兴趣的小伙伴还是可以了解一下的
总结
其实函数柯里化这个概念,可能我们之前没有听说过,但是工作或者学习中我们可能或多或少接触过这种方式,只是不知道这么写就是柯里化,这个函数就是柯里化函数。
柯里化函数这种方式方法还是很实用的,在日常开发过程中还是会遇到需要这种需求的地方。
我是前端战五渣,一个前端界的小学生。