Skip to content

什么是函数柯里化

javascript
const addPrefix = (prefix, arr) => arr.map(item => prefix + item)
const newAPI = addPrefix('/api', ['/getName', '/getAge', '/getAddress'])
console.log(newAPI)
// 结果:['/api/getName', '/api/getAge', '/api/getAddress']

上述函数用于给数组的每一项增加统一前缀

当该函数调用次数较少时没有问题

但是如果该函数需要在多处被调用多次,且需传入相同前缀时

则会导致出现大量重复代码,且耦合程度变大,例如:

javascript
const api1 = addPrefix('/api', [...])
// ...
const api2 = addPrefix('/api', [...])
// ...
const api3 = addPrefix('/api', [...])
// ...
const api4 = addPrefix('/api', [...])

如果需要变更需求,调整 /api/newAPI,则需要一个一个修改或全文件替换

此时想到,用一个变量来代替所有需要用到 /api 的地方:

javascript
const API = '/api'
const api5 = addPrefix(API, [...])

这样后续只需要修改变量的赋值即可

这样可以化解代码耦合程度的问题,但是并没有解决重复代码的问题

并且我们想到,如果此时这个页面还可能需要添加其它的前缀呢

例如有 5 处需要调用函数并添加 /api 这个前缀,但是在另外 6 处需要调用函数添加 /person 这个前缀

这个时候就需要定义两个变量,然后调用十次 addPrefix,并且每一次都要分别传入相应变量

这样做,在后续维护时,由于都是调用 addPrefix,导致逻辑的区分根据传入的变量不同来辨别

而我们希望一个函数只做一件事,且一眼就能看出他是做什么事的

因此可以想到,将 addPrefix 二次封装得到 addApiaddPerson

这样在需要给某处的数组加前缀的时候,我们只需要关心调用哪一个函数

于是得到:

javascript
const addApi = arr => addPrefix('/api', arr) 
const addPerson = arr => addPrefix('/api', arr)
const api1 = addApi([...])
const api2 = addApi([...])
const person1 = addPerson([...])
const person2 = addPerson([...])

经过上述的封装,后续维护就只需要关注 addApiaddPerson 这两个函数

但是又有新的问题出现了,如果需要封装多个这种函数,就需要重新写很多次这个封装过程

因此我们将这个封装的过程也封装到函数内部,重新定义 addPrefix

javascript
const addPrefix = prefix => arr => arr.map(item => prefix + item)

重写后的 addPrefix 接收一个 prefix 作为参数,并返回一个新的函数

该函数接收一个数组,并将之前传入的 prefix 作为前缀,拼接到传入数组的每一项

因此之前的代码又可以改写为:

javascript
// 封装过程被封装
const addApi = addPrefix('/api')
const addPerson = addPrefix('/person')
// 调用还是一样
const api1 = addApi([...])
const api2 = addApi([...])
const person1 = addPerson([...])
const person2 = addPerson([...])

以上过程就是一个最简单的函数柯里化

对任意函数进行柯里化

以上函数只需要两个参数,如果是一个需要三个甚至四个参数的函数,那该如果柯里化呢,其实也是一样的:

例如我们既需要加一个前缀还需要加一个后缀,那我们需要将 addPrefix 再次重写:

javascript
const addPrefix = prefix => {
    // 方便阅读,这里不简写箭头函数的写法
    return suffix => {
        return arr => arr.map(item => prefix + item + suffix)
    } 
}
const addAB = addPrefix('a')('b')
const api = addAB(['1', '2', '3'])
console.log(api)
// ['a1b', 'a2b', 'a3b']

会发现返回一个函数的这个过程越嵌越深,因此我们需要实现一个可以将任意函数柯里化的函数:

javascript
const curry = fn => {
    return function recur (...arg) {
        if (arg.length < fn.length) {
            // 已传入的参数个数,小于函数需要的参数个数
            return (...restArg) => recur(...arg, ...restArg)
        } else {
            // 传入的参数足够,调用函数
            return fn(arg)
        }
    }
}

通过这个函数,就可以传入的函数柯里化,达到之前想要的效果:

javascript
const addPrefix = (prefix, suffix, arr) => arr.map(item => prefix + item + suffix)
const addAB = curry(addPrefix)('a')('b')
const result = addAB(['1', '2', '3'])
const result2 = addAB(['2', '3', '4'])
console.log(result)
console.log(result2)
// ['a1b', 'a2b', 'a3b']
// ['a2b', 'a3b', 'a4b']
0