我为什么要用柯里化?
在说为什么之前,首先把柯里化再解释下

百度词条解释为:

在计算机科学中,柯里化是把接受多个参数的函数转换为接受单一参数的函数,并返回接受余下参数且返回结果的新函数的技术。这个技术由 Christopher Strachey以逻辑学家 Haskell Curry 命名的,尽管它是 Moses Schnfinkel 和 Gottlob Frege发明的。

文字看起来可能有点绕,用代码说明就是:

const add = (a,b)=>{
    return a+b
}
//手动柯里化
const curry = (fn)=>{
    return (b)=>(a)=>{
           return add(a,b)
        }
    
}
//原函数
add(1,2)//3
//柯里化处理后
const curryAdd = curry(add)
const a = curryAdd(1)
const b = a(2) //3

从上面的使用情况,可以很直观的看出来,柯里化进行了参数拆分,但是这样做有什么好处呢

脱离业务来讨论技术大多无意义,我们结合下面的例子一起看下。

在前端项目中,接口请求的封装应该都不陌生。

//request为axios的request请求,做了一些请求拦截处理
为了方便调用,我们简单封装了post和get两种请求方式

//defaultPrefix 是请求的服务名,比如student-web服务,是请求url的一部分
const defaultPrefix = 'XXXX'
export function postRequest(url,data={},prefix){
    return request({
        url: `${prefix || defaultPrefix}/${url}`,
        method: "POST",
        data
    })
}
export function getRequest(url,data,prefix){
	return request({
        url: `${prefix || defaultPrefix}/${url}`,
        method: "GET",
        params:data
    })
}
//分别调用
postRequest('xxx/xxx',{})
getRequest('xxx/xxx',{})

然后你发现这两种方法重复逻辑很多,区别仅仅在于请求方式和传参,于是做了一个整合处理

export function postRequest(method='post',url,data={},prefix){
    const defaultSet = {
        url: `${prefix || defaultPrefix}/${url}`,
        method
    }
    method.toLowerCase() == 'get' ? defaultSet.params = data : defaultSet.data = data
	return request({
        ...defaultSet
    })
}
//调用
postRequest('post','xxx/xxx',{})

随着业务开展,又有返回格式,请求参数头的需求增加,最后请求方式封装成如下:

/*
method:请求方式
options:扩展参数
prefix:请求前缀,默认fontPrefix
url:请求url部分
data:传参
*/
export function postRequest(method,url,options,data={},prefix){
    const headers = data.headers ? data.headers : {}
    data.headers ? delete data.headers :''
    const defaultSet = {
        url: `${prefix || defaultPrefix}/${url}`,
        method,
        headers:{
            ...headers
        },
        ...options
    }
    method.toLowerCase() == 'get' ? defaultSet.params = data : defaultSet.data = data
	return request({
        ...defaultSet
    })
}
//调用一个post请求,该请求返回内容是二进制blob格式
postRequest('post','xxx/xxx/xxx',{
 responseType:"blob"
},{},'')

上面的方法封装完了之后,可以在不同的业务中调用。
但是每次调用都要传好几个参数,而很多参数都是基本固定的,但是也会有少部分请求,参数都不太一样,本着’能懒则懒’的开发原则,柯里化就派上了用场。

lodash工具库里有curry方法可以直接用

import curry from 'lodash/fp/curry'

为了方便理解,我们自己写个简单版的

const defaultPrefix = 'XXXX'
const curry = (fn)=>{
	const fnLen = fn.length//传入的fn的参数个数
	const innerFun = (...params)=>{
        //如果传入的参数个数少于原函数的参数个数,则返回当前函数,并接受余下的参数作为当前函数的参数
            if(params.length <fnLen){
                return innerFun(...params,...selfParam)
            }else{
        //如果传入的参数个数等于原函数的参数个数,执行原函数
                return fn(...params)
            }
	}
        return innerFun
}

const coreRequest = curry((method,options,prefix,url,data)=>{
	const dataSet = {}
	method.toLowerCase() == 'get' ? dataSet.params = data : dataSet.data = data
	return request({
		url:prefix+url,
		method,
		...dataSet,
		...options
	})
})
//常规json格式请求
const jsonContentType = {
	headers:{
		'Content-Type':'application/json;charset=utf-8'
	}
}
//返回带第一个method参数的函数
const post = coreRequest('post')
const get = coreRequest('get')
//返回请求头部是application/json的post请求函数
const postJson = post(jsonContentType)
返回请求头部是application/json的get请求函数
const getJson = get(jsonContentType)
//返回带默认请求头的post请求
const postApi = postJson(defaultPrefix)
//返回带默认请求头的get请求
const getApi = postJson(defaultPrefix)

//业务调用地方传入url参数
const requestFun = postApi('xxxxxx')
//传入data参数
requestFun({
    name:1
})
如果需要修改请求url的服务,改为teacher-web,可以重新定义一个函数如下:
const postDefineApi = postJson('teacher-web')
requestFun({
    name:1
})

如果还有别的请求接口需求,可以根据coreRequest做不同的扩展返回不同的函数

上面还有个问题,如果requestFun不传参数(实际情况我们的接口请求,body数据或者param是有可能空的),那么curry方法不会走到最后一步,为了兼容这种情况,可以将curry做如下修改

const curry = (fn)=>{
    const fnLen = fn.length//传入的fn的参数个数
    const innerFun = (...params)=>{
        if(params.length <fnLen){
        //如果传入的参数个数少于原函数的参数个数,
        //且后面还有参数则返回当前函数,并接受余下的参数作为当前函数的参数,否则执行函数
            return (...selfParam)=>{
                if(!selfParam || selfParam.length == 0){
                        return fn(...params)
                    }else{
                        return innerFun(...params,...selfParam)
                    }
                }
	}else{
            //如果传入的参数个数等于原函数的参数个数,执行原函数
            return fn(...params)
        }
    }
	return innerFun
}

这样就兼顾了减少代码量和扩展性

总结下来:
个人认为柯里化有如下优缺点:

  • 优点:节省重复传参,函数更灵活,扩展性高
  • 缺点:如果参数多,返回的接受函数较多,看上去有点绕,理解上没有一个函数直观。且因为是闭包,可能有内存泄漏风险

当然是否采用柯里化函数,结合实际业务需求来即可。