资讯专栏INFORMATION COLUMN

谈谈柯里化方式的累加器实现

魏宪会 / 2383人阅读

摘要:最直接的方式当然是遍历数组并累加得到结果,也可以使用数组的方法实现,如下结合第四步,替换中的返回值即可将其进行简化,得到最终结果当然,采用这种实现方式,对于形如的调用方式也是没有问题的。

谈谈 JavaScript 中形如 add(1)(2)(3)(4) = 10 这种累加器方法的实现过程和思路

第一步:实现级联

若是想要实现 fn()() 这种调用方式的函数,则在 fn 函数体内一定也会返回一个函数,如下:

function fn(){
    return function(){
        
    }
}

再进一步,若是想要实现 fn()()()... 不定次数的调用,则意味着每一层的返回值均为一个函数,这就需要使用类似递归的方式去实现:

function fn(){
    var _fn = function(){
        return _fn;
    }
    return _fn;
}
第二步:实现返回值

如果每一层的返回值均为函数,那么如何返回累加结果呢?也就是说,在函数调用的最后一层,这个返回值应该是一个值而非函数。这里则需要使用 valueOf 这一方法,例子如下:

function fn1(){

}
console.log(fn1); // function ...

function fn2(){
    
}
fn2.valueOf = function(){
    return 1;
}
console.log(fn2); // function 1

注意,fn2 在控制台的输出结果为 function 1,但其数值型隐式转换的结果为 1,也就是说 console.log(+fn2) 或是 var num = fn2 + 1 是可以正常地作为数值型进行计算的。而 fn2() 的方式仍然可以将其作为函数运算。

因而,我们只要给 _fn 添加 valueOf 方法就可以实现在不定次调用之后,得到一个「值」而非「函数」,如下:

function fn(){
    var _fn = function(){
        return _fn;
    }
    _fn.valueOf = function(){
        return 12345;
    }
    return _fn;
}

这里也可以使用添加 toString() 的方式实现这一功能,与 valueOf() 做法一致。

第三步:获得传入的参数

这一步相对简单,使用内置对象 arguments 即可实现对函数中所传入的参数的获取,如下:

function foo(){
    console.log(arguments);
}

foo(1,2,3);

注意,这里的 arguments 不是数组,而是对象:

{
    0 : 1,
    1 : 2,
    2 : 3,
    callee : function foo(),
    length : 3,
    Symbol(Symbol.iterator) : function values(),
    __proto__ : Object,
}

若要将其转换为数组,可以使用如下方式:

var arr = [].slice.call(arguments);
// 或是
var arr = Array.prototype.slice.call(arguments);

若一个对象含有 length 属性,则可以通过这种方式转换为数组形式

第四步:实现参数保存

add(1)(2)(3)... 累加器是在最后一次调用后返回之前所有参数的累加和。也就是说我们需要有一个地方可以保存先前的值或是计算结果。

在之前的代码框架下,显然不能将其保存在内层的 _fn 中。因为每层调用都相当于又一次的 _fn() 执行,在其中定义的变量会被覆盖。

使用全局变量当然是一种方式,但是这样会污染全局空间,不是最佳方案。

考虑到对 fn()()()... 的调用实际返回的是内层的 _fn,意味着 fn 的局部变量其实也相当于 _fn 的全局变量。因而可以将保存先前参数的职责交给 fn 中的一个变量,代码如下:

function fn(){
    var numList = [];
    var _fn = function(){
    
        // 这里测试思路是否可行
        numList.push(1);
        console.log(numList);
        
        return _fn;
    }
    _fn.valueOf = function(){
        return 12345;
    }
    return _fn;
}

console.log(fn()()()); // [1, 1]
// 注意这里虽然调用三次,但实际只执行了两次 push(1),第一次调用没有执行内层的 _fn(),而只是返回了它。

结合第三步,我们可以通过 push() 或是 concat() 的方式将每一次的参数组合起来,如下:

function fn(){
    var numList = [].slice.call(arguments);
    var _fn = function(){
        // 注意这里的 arguments 是传入 _fn 的参数
        var innerArguments = [].slice.call(arguments);
        numList = numList.concat(innerArguments);
        console.log(numList);
        
        return _fn;
    }
    _fn.valueOf = function(){
        return 12345;
    }
    return _fn;
}

console.log(fn(1)(2)(3)); // [1, 2, 3]

当然,这里也可以使用 push() 的方式,将每一次的参数推入数组。

这一步还有另一种思路:用每一次的求和代替参数数组的合并。

第五步:求和计算

既然已经得到了全部的参数集合,对其进行求和就比较简单了。最直接的方式当然是遍历数组并累加得到结果,也可以使用数组的 reduce 方法实现,如下:

var arr = [1, 2, 3];
var sum = arr.reduce(function(num1, num2){
    return num1 + num2;
});
console.log(sum); // 6

结合第四步,替换 valueOf 中的返回值即可:

function fn(){
    var numList = [].slice.call(arguments);
    var _fn = function(){
        var innerArguments = [].slice.call(arguments);
        numList = numList.concat(innerArguments);
        
        return _fn;
    }
    _fn.valueOf = function(){
        return numList.reduce(function(num1, num2){
            return num1 + num2;
        });
    }
    return _fn;
}

console.log(fn(1)(2)(3));

将其进行简化,得到最终结果:

function fn(){
    var numList = [].slice.call(arguments);
    var _fn = function(){
        numList = numList.concat([].slice.call(arguments));
        return _fn;
    }
    _fn.valueOf = function(){
        return numList.reduce(function(i, j){return i+j;});
    }
    return _fn;
}

当然,采用这种实现方式,对于形如 fn(1, 2, 3)(4)(5, 6, 7) 的调用方式也是没有问题的。

参考

前端基础进阶(八):深入详解函数的柯里化 - 简书

JS中的call()和apply()方法 - ITeye

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/81858.html

相关文章

  • 从一道面试题谈谈函数柯里(Currying)

    摘要:忍者秘籍一书中,对于柯里化的定义如下在一个函数中首先填充几个参数然后再返回一个新函数的技术称为柯里化。回到我们的题目本身,其实根据测试用例我们可以发现,函数的要求就是接受单一函数,例如但是与柯里化不同之处在于,柯里化返回的一个新函数。   欢迎大家再一次来到我的文章专栏:从面试题中我们能学到什么,各位同行小伙伴是否已经开始了悠闲的春节假期呢?在这里提前祝大家鸡年大吉吧~哈哈,之前有人说...

    cppprimer 评论0 收藏0
  • 「前端面试题系列6」理解函数柯里

    摘要:原题如下写一个方法,当使用下面的语法调用时,能正常工作这道题要考察的,就是对函数柯里化的理解。当参数只有一个的时候,进行柯里化的处理。这其实就是函数柯里化的简单应用。 showImg(https://segmentfault.com/img/bVbopGm?w=620&h=350); 前言 这是前端面试题系列的第 6 篇,你可能错过了前面的篇章,可以在这里找到: ES6 中箭头函数的...

    liaorio 评论0 收藏0
  • js 扩展 -- currying 柯里函数

    摘要:里也有柯里化的实现,只是平时没有在意。如果函数柯里化后虽然生搬硬套,不过现实业务也会有类似场景。 柯里化 先解释下什么是 柯里化 在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。 js 里也有柯里化的实现,只是平时没有在意。先把原文简介贴...

    Pocher 评论0 收藏0
  • JavaScript函数式编程入门经典

    摘要:函数式编程的定义函数是一段可以通过其名称被调用的代码。纯函数大多数函数式编程的好处来自于编写纯函数,纯函数是对给定的输入返回相同的输出的函数,并且纯函数不应依赖任何外部变量,也不应改变任何外部变量。 一个持续更新的github笔记,链接地址:Front-End-Basics,可以watch,也可以star。 此篇文章的地址:JavaScript函数式编程入门经典 正文开始 什么是函...

    silvertheo 评论0 收藏0
  • 邂逅函数柯里

    摘要:柯里化函数的作用函数柯里化允许和鼓励你分隔复杂功能变成更小更容易分析的部分。指向的是调用方法的一个函数,绑定,延迟执行可见,之后返回的是一个延迟执行的新函数关于性能的备注通常,使用柯里化会有一些开销。 引子 有这样一道题目,实现一个函数,实现如下功能: var result = sum(1)(2)(3); console.log(result);//6 这道题目,印象中是一道技术笔试...

    Kross 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<