一道关于柯里化的前端面试题

JS 类型转换及柯里化

实现一个函数,运算结果可以满足如下预期结果:

add(1)(2) // 3
add(1, 2, 3)(10) // 16
add(1)(2)(3)(4)(5) // 15

先上本人自己的答案:

function add(){
    // 第一次执行时,定义一个数组专门用来储存所有的参数
    var args = [].slice.call(arguments);
    // 在内部声明一个函数,利用闭包的特性保存_args并收集所有的参数值
    var fn = function(){
        var fn_args = [].slice.call(arguments);

        return add.apply(null, args.concat(fn_args));
    }
    // 利用隐式转换的特性,当最后执行时隐式转换,并计算最终的值返回
    fn.toString = function(){
        return args.reduce(function(acc, prev){
            return acc + prev;
        })
    }

    return fn;
}

看完是不是觉得云里雾里,不如copy;
接下来,和大家一起理一理这道题的知识点:

  1. 函数的隐式转换

    JavaScript作为一种弱类型语言,它的隐式转换是非常灵活有趣的。当我们没有深入了解隐式转换的时候可能会对一些运算的结果会感动困惑,比如4 + true = 5

    先来一些简单的思考题。

    function fn(){
       return 20;
    }
    console.log(fn + 10); // 输出结果是多少?

    稍微修改一下,再想想输出结果会是什么?

    function fn() {
       return 20;
    }
    fn.toString = function() {
       return 10;
    }
    
    console.log(fn + 10);  // 输出结果是多少?

    还可以继续修改一下。

    function fn() {
       return 20;
    }
    
    fn.toString = function() {
       return 10;
    }
    
    fn.valueOf = function() {
       return 5;
    }
    
    console.log(fn + 10); // 输出结果是多少?

    以上的输出结果分别为

    function fn() {
       return 20;
    }10
    
    20
    
    15

    当使用console.log,或者进行运算时,隐式转换就可能会发生。从上面三个例子中我们可以得出一些关于函数隐式转换的结论。

    当我们没有重新定义toStringvalueOf时,函数的隐式转换会调用默认的toString方法,它会将函数的定义内容作为字符串返回。而当我们主动定义了toString/vauleOf方法时,那么隐式转换的返回结果则由我们自己控制了。

    注:其中valueOf的优先级会toString高一点。

  2. 利用call/apply

  3. 由浅入深的柯里化

    在开头提到的题目,很明显,计算结果正是所有参数的和,add方法每运行一次,肯定返回了一个同样的函数,继续计算剩下的参数。

    我们可以从最简单的例子一步一步寻找解决方案。

    add(1)(2); // 3
    function add(a){
       return function(b){
           return a + b;
       }
    }
    
    add(1)(2)(3); // 6
    function add(a){
       return function(b){
           return function(c){
               return a + b + c;
           }
       }
    }

    上面的封装看上去跟我们想要的结果有点类似,但是参数的使用被限制得很死,因此并不是我们想要的最终结果,我们需要通用的封装。应该怎么办?总结一下上面2个例子,其实我们是利用闭包的特性,将所有的参数,集中到最后返回的函数里进行计算并返回结果。因此我们在封装时,主要的目的,就是将参数集中起来计算。

    现在回头再看看上面的答案,是不是没那么迷糊了。

    function add(){
       // 第一次执行时,定义一个数组专门用来储存所有的参数
       var args = [].slice.call(arguments);
       // 在内部声明一个函数,
       // 利用闭包的特性保存_args并收集所有的参数值
       var fn = function(){
           var fn_args = [].slice.call(arguments);
    
           return add.apply(null, args.concat(fn_args));
       }
       // 利用隐式转换的特性,
       // 当最后执行时隐式转换,并计算最终的值返回
       fn.toString = function(){
           return args.reduce(function(acc, prev){
               return acc + prev;
           })
       }
    
       return fn;
    }

    那么从这里就要引出柯里化这个定理:

    柯里化(英语:Currying),又称为部分求值,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回一个新的函数的技术,新函数接受余下参数并返回运算结果。

    • 接收单一参数,因为要携带不少信息,因此常常以回调函数的理由来解决。
    • 将部分参数通过回调函数等方式传入函数中
    • 返回一个新函数,用于处理所有的想要传入的参数

    在上面的例子中,我们可以将add(1, 2, 3, 4)转换为add(1)(2)(3)(4)。这就是部分求值。每次传入的参数都只是我们想要传入的所有参数中的一部分。

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值