面试题:手写JS函数柯里化

5,556 阅读4分钟

一、柯里化定义:

在计算机科学中,柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。

定义是什么意思?举个例子说明

function add(a, b, c){}
// 正常的调用方式
add(1,2,3)
// 而柯里化是把这个函数变成另一个函数f,
// 这个函数f接收add的第一个参数,
// 并且这个函数返回又一个函数f1,
let f1 = f(1)
// f1接收add另外两个参数,并返回结果
f1(2,3)
// f(1)(2,3)和add(1,2,3)是等效的

 上面的例子没看明白?那好。就更表面化的解释一下:

function add(a, b, c){}
// 还是一个接收三个参数的函数
add(1,2,3)
// 柯里化就是把上面的调用方式变成下面的样子
f(1)(2,3)
// 上面两种调用方式,执行结果是一样的
// 再进一步柯里化
f(1)(2)(3)
// 上面三种调用方式等效

二、柯里化有什么用?

柯里化最大的应用场景就是实现偏函数。

// 例如一个ajax方法
function getApi(url, username ,password, config){}
// 这个方法,前三个参数并不是频繁变化
// 每次调用都会传,麻烦不说,参数很长看着也不爽
// 我们就定义几个偏函数,
let getBaiduData = getApi("http://baidu.com/aaa/bbb/ccc","tom","123456")
let getQQData = getApi("http://www.qq.com/eee/fff/ggg","jerry","123456")
// 这样后面调用就很方便了,维护起来也方便
let baidu = getBaiduData({method:get})
let qq = getQQData ({method:post})
let baidu2 = getBaiduData({method:post})

上面的例子很粗糙,总之要说明的意思就是,偏函数能简化代码量,增加代码可重复率。大家发挥一下,找到更多的的利用方式。

三、柯里化如何实现:

不多说先上实现过程;很简单,一看就能懂:

function toCurry(func, ...args) {
    // ↑需要柯里化的函数作为参数
    // ↑也可以有初始参数传入
    // ↑缓存在args中return function () {
        // 合并上一次缓存的参数和本次传入的参数
        args = [...args,...arguments];
        // 判断参数数量是否足够
        if (args.length < func.length) {
            // 如果不够,继续递归
            // 注意,这里每一次递归都会形成新的闭包
            // 保证柯里化函数每一步调用都是独立的,互不影响
            return toCurry(func, ...args);
        } else 
           // 如果参数满足数量,执行函数并返回结果
            return func.apply(null, args);
        }
    }
}

简单解释一下:
1、用到知识点是闭包和递归
2、Function.length方法要说明一下,返回该函数预接收参数的个数。

思路是利用闭包的原理,先把函数和参数缓存起来,并返回一个能接收剩余参数的函数。继续调用,如果参数个数不够则继续递归。当参数的个数满足函数预接收的个数,执行函数,返回结果。

上面的代码怎么用:函数直接穿进去就可以了

function bar(a, b, c) {
    return a + b + c;
}
// 把函数传进去就可以了
var f = toCurry(bar)
​
console.log(f(1)(2)(3));
console.log(f(1)(2, 3));
console.log(f(1, 2)(3));

四、进阶写法

function toCurry(func, ...args) {
    // 判断func是否为函数,毕竟柯里化要操作的是函数
    if (!func instanceof Function) throw TypeError();
    // 设置length属性为剩余参数的个数
    // 为什么这么写
    // 因为Function.length是不可写的writable:false
    // 但是是可配置的,configurable:true
    Object.defineProperty(curry,'length',{
        get:function () {
            return func.length - args.length
        }
    })
    // 定义rest属性,用来判断是否还有剩余参数
    curry.rest = curry.length > 0;
    
    function curry() {
        args = [...args, ...arguments];
​
        if (args.length < func.length) {
            return toCurry(func, ...args);
        } else {
            // 这里apply第一参数改成了this,您考虑一下为什么呢?
            return func.apply(this, args);
        }
    }
    return curry
}

五、局限性

上面柯里化的实现方法中,用到了Function.length属性获取函数形参数量,但是Function.length参数的数量是第一个默认参数之前的参数的数量,也不包括rest参数。
例如

function add(a, b, c){}//add.length = 3
function add(a, b, c = 1){}//add.length = 2
function add(a, b = 1, c){}//add.length = 1
function add(a, b, ...c){}//add.length = 2

暂时没有想到如何做到更广泛的适应性。希望高手能指点。

     ↓↓↓↓↓↓扫描关注公众号↓↓↓↓↓↓