基本概念
上面是百度百科以及维基百科关于柯理化的定义,单纯从字面上面理解是很困难的。 简单说,柯里化(Currying)是一种处理多元函数的方法。它产生一系列连锁函数,其中每个函数固定部分参数,并返回一个新函数,用于传回其它剩余参数的功能。
下面我们通过一些实例,拆解和说明一下柯理化的具体含义。
这是一个普通的三元函数,执行运算得出三个参数的和。
function add(a,b,c){
return a + b + c
}
add(1,2,3) // 6
按照上面的定义,add函数的的柯理化转换过程:add(a,b,c) => curriedAdd(a)(b)(c)。
实现代码如下:
function curriedAdd(a){
return function(b){
return function(c){
return a + b + c
}
}
}
执行验证
add(1,2,3) // 6
curriedAdd(1)(2)(3) // 6
上面是基于极简场景的实现,完整的curriedAdd函数,应当是满足下面的调用场景:
-
初始传入一个参数,curriedAdd(1)(2,3) 或者 curriedAdd(1)(2)(3)
- 如果首次执行curriedAdd只传入一个参数,那么将返回一个接受剩余两个参数的函数currying1
- 如果继续执行currying1,并传入两个参数,将返回执行结果a+b+c;但是,如果继续执行currying1,并传入一个参数,那么将返回一个接受第三个参数的函数currying2
- 如果继续执行curryring2,并传入参数,将返回执行结果a+b+c
-
初始传入两个参数,curriedAdd(1,2)(3)
- 如果首次执行curriedAdd,并传入两个参数,那么将返回一个接受最后一个参数的函数curryring1
- 如果继续执行curryring1,并传入参数,将返回执行结果a+b+c
-
初始传入三个参数,curriedAdd(1,2,3)
- 如果首次执行curriedAdd,直接传入三个参数,那么将直接返回执行结果a+b+c
把上述拆解分析的逻辑过程,转化成函数:
function curriedAdd() {
let args = [].slice.call(arguments)
if (args.length >= 3) {
return args[0] + args[1] + args[2]
} else if (args.length === 2) {
return function () {
return args[0] + args[1] + arguments[0]
}
} else if (args.length === 1) {
return function c() {
let args1 = [].slice.call(arguments)
if (args1.length >= 2) {
return args[0] + args1[0] + args1[0]
} else if(args1.length === 1) {
return function () {
return args[0] + args1[0] + arguments[0]
}
}else{
return c
}
}
} else {
return curriedAdd
}
}
执行验证
console.log(curriedAdd(1)) // [Function (anonymous)]
console.log(curriedAdd(1)(2)) // [Function (anonymous)]
console.log(curriedAdd(1)(2)(3)) // 6
console.log(curriedAdd(1, 2)) // [Function (anonymous)]
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1, 2, 3)) // 6
验证实例运行结果:
[Function (anonymous)]
[Function (anonymous)]
6
[Function (anonymous)]
6
6
总之,柯理化是把一个多元函数,转换成一系列更少元函数的处理方法。
实现原理
实现一个二元或者三元的柯理化函数相对简单,就像上面的curriedAdd函数。难的是实现一个任意元函数的通用柯理化函数。 基于上面的分析,实现一个通用的柯理化函数,需要以下几个条件: 1、首先需要一个函数fn作为参数; 2、其次,可以获取到fn声明时虚参的数量,通过fn.length属性可以实现; 3、最后,可以判断返回接受剩余参数的新函数,或者返回fn(...参数)执行结果,以及缓存已经固定的参数。通过fn.length、闭包和递归可以实现。
function currying(fn) {
return function curried() {
var args = [].slice.call(arguments),
context = this
return args.length >= fn.length ?
fn.apply(context, args) :
function () {
var rest = [].slice.call(arguments)
return curried.apply(context, args.concat(rest))
}
}
}
通过currying创建上面的curriedAdd函数,执行验证得出相同的结果。
var curriedAdd = currying(add)
console.log(curriedAdd(1)) // [Function (anonymous)]
console.log(curriedAdd(1)(2)) // [Function (anonymous)]
console.log(curriedAdd(1)(2)(3)) // 6
console.log(curriedAdd(1, 2)) // [Function (anonymous)]
console.log(curriedAdd(1, 2)(3)) // 6
console.log(curriedAdd(1, 2, 3)) // 6
此外,还有一个问题需要补充说明。对参数不固定的函数进行柯理化变换是没有意义的。 例如,下面这个对不定数量的数字进行排序的sort函数。函数声明时参数的数量并不确定。通过sort.length获取到的虚参的数量是0,无论给curriedSort传入多少参数都会立即执行。
function sort(){
return [].slice.call(arguments).sort(function(a,b){
return a-b
})
}
sort(1,3,6,2) // [1,2,3,6]
var curriedSort = currying(sort)
var currying1 = curriedSort(1,3,6,2) // [1,2,3,6]
currying1(5) // TypeError: curriedSort(...) is not a function
虽然无法通过length属性获取到不确定参数的长度,但是可以再柯理化转换的同时,指定目标参数长度,用于替代sort.length的作用。下面调整一下currying,适配参数不固定的函数。
function currying(fn, len) {
return function curried() {
var args = [].slice.call(arguments),
context = this
var _len = fn.length || len
return args.length >= _len ?
fn.apply(context, args) :
function () {
var rest = [].slice.call(arguments)
return curried.apply(context, args.concat(rest))
}
}
}
var curriedSort = currying(sort,5)
var currying1 = curriedSort(1,3,6,2) // [Function (anonymous)]
currying1(5) // [1,2,3,5,6]
应用实践
- 解决重复传参问题,提高函数适用性
柯理化(currying)应用很广泛也很常见。比如,批量发送双11活动邮件,通常我们这样做
function sendEmail(from, content, to){
console.log(`${from} send email to ${to}, content is ${content}`)
}
sendEmail('xx公司', '双11优惠折上5折', 'zhangsan@xx.com')
sendEmail('xx公司', '双11优惠折上5折', 'lisi@xx.com')
sendEmail('xx公司', '双11优惠折上6折', 'wangwu@xx.com')
sendEmail('xx公司', '双11优惠折上6折', 'maliu@xx.com')
// ...
邮件发送方是固定的,邮件内容是相对固定的,唯一不同的是邮件的接受者。这正符合柯理化(currying)固定部分参数,并返回接受剩余参数新函数的规则。柯理化创建两个临时性的、适用性更强的函数sendEmailToS5和sendEmailToS6,向目标群体,发送指定类型的邮件。
var sendEmailContent = currying(sendEmail)('xx公司')
var sendEmailToS5 = sendEmailContent('双11优惠折上5折')
var sendEmailToS5 = sendEmailContent('双11优惠折上6折')
// 打五折的群组
sendEmailToS5('zhangsan@xx.com')
sendEmailToS5('lisi@xx.com')
// ...
// 打六折的群组
sendEmailToS6('wangwu@xx.com')
sendEmailToS6('maliu@xx.com')
// ...
因此,柯理化(currying)可以解决重复传参的问题,并提高函数功能的适用性。
- 降低函数参数元次,适配应用
通常,在创建工具函数时,我们尽量使其更加抽象,以提高其通用性。但是,这样做的弊端也很明显,会降低其适用性。比如,我们创建一个获取对象目标属性的函数getObjKeys(obj,keys)
function getObjKeys(keys, obj){
var o = {}
keys.forEach(function(k){
o[k] = obj[k]
})
return o
}
var person = {
name:'zhangsan',
age: 20,
work: 'worker',
tel: '13699887766'
}
getObjKeys(['name','tel'], person) // {name:'zhangsan',tel:'13699887766'}
假设,另外一个场景,我们需要查询车间,所以worker的姓名、年龄和电话。我们可以这样做
workers.map(function(worker){
return getObjKeys(worker,['name','age','tel'])
})
除此之外,利用柯理化,我们可以固定getObjKeys函数keys参数,同时得到一个接受另外一个参数obj的函数。这个函数,可以作为map函数的callback直接被使用。
var callback = currying(getObjKeys)(['name','age','tel'])
workers.map(callback) // [{name,age,tel},...]
参考资料
baike.baidu.com/item/%E6%9F… github.com/shfshanyue/… juejin.cn/post/684490… developer.mozilla.org/zh-CN/docs/… www.yuque.com/webqiang/qn… github.com/mqyqingfeng…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!