拼图:JS函数返回自己,直到没有参数

时间:2014-08-30 03:26:51

标签: javascript

我试图解决一个谜题,并且在我的智慧结束时试图弄明白。

我应该制作一个像这样的功能:

add(1);       //returns 1
add(1)(1);    //returns 2
add(1)(1)(1); //returns 3

我知道可以做到,因为其他人已成功完成拼图。我尝试了几种不同的方法。这是我最近的尝试:

function add(n) {
    //Return new add(n) on first call
    if (!(this instanceof add)) {
        return new add(n);   
    }

    //Define calc function
    var obj = this;
    obj.calc = function(n) {
        if (typeof n != "undefined") {
            obj.sum += n;
            return obj.calc;            
        }

        return obj.sum;
    }

    //Constructor initializes sum and returns calc(n)
    obj.sum = 0;
    return obj.calc(n);
}

我们的想法是,在第一次调用时,会初始化新的add(n)并运行calc(n)。如果calc收到参数,则会将n添加到sum并返回自身。当它最终没有收到参数时,它返回值sum

理论上这是有道理的,但我不能让它发挥作用。有什么想法吗?

- 编辑 -

我的代码就是我选择的路线。如果有人能想出一个方法,我不反对采用不同的方法。

5 个答案:

答案 0 :(得分:10)

回答"如何运作"。给出:

function add(n) {
    function calc(x) {
        return add(n + x);
    }
    calc.valueOf = function() {
        return n;   
    }
    return calc;
}

var sum = add(1)(2)(3); // 6

第一次调用 add 时,它会将传入的值存储在名为 n 的变量中。然后它返回函数 calc ,它具有 n 的闭包和特殊的 valueOf 方法(稍后解释)。

然后使用值2调用此函数,因此它调用 add ,其总和为n + x,其中1 + 2为3。

因此返回了新版本的 calc ,这次使用 n 的闭包值为3.

这个新的 calc 的值为3,因此它使用n + x调用 add ,这次是3 + 3,这是typeof sum == 'function'; 6

再次添加会返回一个新的 calc n 设置为6.最后一次, calc isn&# 39;再次打来电话。返回的值将分配给变量 sum 。所有 calc 函数都有一个特殊的 valueOf 方法,它取代了Object.prototype提供的标准方法。通常 valueOf 只会返回函数对象,但在这种情况下,它将返回 n 的值。

现在 sum 可用于表达式,如果调用 valueOf 方法,它将返回6(即 n 的值保持不变在封闭中。)

这看起来很酷,而且 sum 的行为很像一个原始数字,但它实际上是一个功能:

sum * 2   // 12
sum == 6  // true

sum === 6 // false -- oops!!

因此要小心严格测试事物的类型:

{{1}}

答案 1 :(得分:4)

这是一个有点精简的@ RobG版本的答案:

function add(n) { 
    function calc(x) { return n+=x, calc; }
    calc.valueOf = function() { return n; };
    return calc;
}

细微差别在于calc只更新n然后返回自身,而不是通过另一个调用add来返回自身,这会将另一个帧放在堆栈上。

明确自我复制

因此,{p> calc是一个纯粹的自我复制功能,它会自行返回。我们可以用函数

封装“自我复制”的概念
function self_replicate(fn) {
    return function x() {
        fn.apply(this, arguments);
        return x;
    };
}

然后add可能会以更加自我记录的方式编写

function add(n) { 
    function update(x) { n += x; }
    var calc = self_replicate(update);
    calc.valueOf = function() { return n; };
    return calc;
}

与Array#reduce

并行

请注意,重复调用函数的方法与Array#reduce之间存在一定的并行性。两者都将一系列事物简化为单一值。在Array#reduce的情况下,列表是一个数组;在我们的例子中,列表是重复调用的参数。 Array#reduce定义了reducer函数的标准签名,即

function(prev, cur)

其中prev是“累加器”(到目前为止的值),cur是输入的新值,返回值成为累加器的新值。重写我们的实现以使用具有这种签名的函数似乎很有用:

function add(n) { 
    function reducer(prev, cur) { return prev + cur; }
    function update(x) { n = reducer(n, x); }
    var calc = self_replicate(update);
    calc.valueOf = function() { return n; };
    return calc;
}

现在我们可以创建一种更通用的方法来基于reducer函数创建基于自复制的Reducer:

function make_repeatedly_callable_function(reducer) {
    return function(n) { 
        function update(x) { n = reducer(n, x); }
        var calc = self_replicate(update);
        calc.valueOf = function() { return n; };
        return calc;
    };
}

现在我们可以创建add作为

var add = make_repeatedly_callable_function(function(prev, cur) { return prev + cur; });
add(1)(2);

实际上,Array#reduce使用第三个和第四个参数调用reducer函数,即数组索引和数组本身。后者在这里没有任何意义,但是可以想象我们可能想要像第三个参数那样知道我们正在进行什么“迭代”,这很容易通过使用变量i来跟踪:

function reduce_by_calling_repeatedly(reducer) {
    var i = 0;
    return function(n) { 
        function update(x) { n = reducer( n, x, i++); }
        var calc = self_replicate(update);
        calc.valueOf = function() { return n; };
        return calc;
    };
}

替代方法:跟踪价值

跟踪调用函数的中间参数(使用数组),然后在最后执行reduce而不是我们继续执行时,有一些优点。例如,那么我们可以做Array#reduceRight类型的事情:

function reduce_right_by_calling_repeatedly(reducer, initialValue) {

    var array_proto = Array.prototype, 
        push = array_proto.push, 
        reduceRight = array_proto.reduceRight;

    return function(n) { 
        var stack=[],
            calc = self_replicate(push.bind(stack));

        calc.valueOf = reduceRight.bind(stack, reducer, initialValue);
        return calc(n);
    };
}

非原始对象

让我们尝试使用这种方法来构建(“扩展”)对象:

function extend_reducer(prev, cur) {
    for (i in cur) {
      prev[i] = cur[i];
    }
    return prev;
}

var extend = reduce_by_calling_repeatedly(extend_reducer);
extend({a: 1})({b: 2})

不幸的是,这不起作用,因为只有当JS需要一个原始对象时才会调用Object#toValue。因此,在这种情况下,我们需要明确地调用toValue

extend({a: 1})({b: 2}).toValue()

答案 2 :(得分:1)

感谢valueOf()提示。这是有效的:

function add(n) {
    var calc = function(x) {
        return add(n + x);
    }

    calc.valueOf = function() {
        return n;   
    }

    return calc;
}

- 编辑 -

  

请您解释一下这是如何工作的?谢谢!

我不知道我是否知道正确的词汇来准确描述它的工作原理,但我会尝试:

示例陈述:add(1)(1)

调用add(1)时,会返回对calc的引用。

  1. calc了解n是什么,因为在" mind"在解释器中,calcadd的函数子函数。当calc查找n但未在本地找到时,它会搜索scope chain并找到n

  2. 因此,当调用calc(1)时,它会返回add(n + x)。请注意,calc知道n是什么,x只是当前的参数(1)。添加实际上是在calc内完成的,因此此时会返回add(2),然后返回对calc的另一个引用。

  3. 每当我们有另一个论点(即(x))时,步骤2就会重复。

    • 如果没有任何参数,我们只剩下definition calc。实际上从未调用过去的calc,因为您需要()来调用函数。此时,通常解释器将返回calc的函数对象。但是,因为我覆盖了calc.valueOf,所以它会运行该函数。

    • calc.valueOf运行时,它会在范围链中找到n的最新实例,这是之前所有n的累计值。

    我希望有些感觉。我刚刚看到了@RobG的解释,这无疑比我的要好得多。如果你感到困惑,请阅读那篇文章。

答案 3 :(得分:0)

以下是使用bind的变体:

var add = function _add(a, b) {
    var boundAdd = _add.bind(null, a + b);

    boundAdd.valueOf = function() { 
        return a + b; 
    }

    return boundAdd;
}.bind(null, 0);

我们正在利用bind的一个功能,它允许我们在我们绑定的函数上设置默认参数。来自文档:

  

bind()还接受提供给目标的前导默认参数   调用绑定函数时的函数。

因此,_add充当一种函数,它接受两个参数ab。它返回一个新函数boundAdd,它是通过将原始_add函数的a参数绑定到a + b来创建的。它还有一个重写的valueOf函数,它返回a + b valueOf函数在@ RobG的答案中得到了很好的解释)

要获取初始add功能,我们bind _add a参数0

然后,当add(1)被调用时,a = 0(来自我们最初的bind调用)和b = 1(传递参数)。它返回一个新函数a = 1(绑定到a + b)。

如果我们然后使用(2)调用该函数,则会设置b = 2并且它将返回a = 3的新函数。

如果我们然后使用(3)调用该函数,则会设置b = 3并且它将返回a = 6的新函数。

依此类推,直到valueOf被调用,此时它将返回a + b。在add(1)(2)(3)之后,3 + 3

答案 4 :(得分:0)

这是一种非常简单的方法,它符合OP所寻求的标准。即,函数传递一个整数,跟踪该整数,并将自身作为函数返回。如果未传递参数 - 该函数返回传递给它的整数之和。

let intArray = [];
function add(int){
    if(!int){
        return intArray.reduce((prev, curr) => prev + curr)
    }
    intArray.push(int)
    return add   
}

如果您这样称呼:

console.log(add(1)(1)()); 

输出2。