Closure in Javascript with multiple brackets

2019-03-09 20:43发布

问题:

Could any one explain how this function alert, when more no of brackets of parameters are passed. I could not able to understand it clearly.

function sum(a) {

  var sum = a

  function f(b) {
    sum += b
    return f
  }

  f.toString = function() { return sum }

  return f
}

alert( sum(1)(2) )  // 3
alert( sum(5)(-1)(2) )  // 6
alert( sum(6)(-1)(-2)(-3) )  // 0
alert( sum(0)(1)(2)(3)(4)(5) )  // 15

回答1:

The first time your function is called, the first value is stored in sum. After that function f(b) will be returned, maintaining the provisional result in sum. With each consecutive call you execute function f - you perform sum += b and return f again. If a string context is required (such as in the alert, or a console.log) f.toString is called instead, returning the result (sum).

function sum(a) {

  var sum = a

  function f(b) {
    sum += b
    return f  //<- from second call, f is returned each time
              //   so you can chain those calls indefinitely
              //   function sum basically got "overridden" by f
  }

  f.toString = function() { return sum }

  return f //<- after first call, f is returned
}

Explanation:

alert( sum(6)(-1)(-2)(-3) )  // 0
           /\ function sum called, f returned
              /\ the returned function f is called, f returns itself
                  /\ again
                     /\ and again
                         /\ at last, alert() requires string context,
                            so f.toString is getting invoked now instead of f


回答2:

The thing to look at is this piece of code

function f(b) {
    sum += b
    return f
  }

This function returns reference to itself so it can be called as many times as possible. An important thing about it is that it has a tostring function that gets called and since tostring is defined inside function sum() it has access to the variable sum and its value current value (that is changed by f() )



回答3:

alert expects a string. If it doesn't get a string, it will attempt to convert whatever object it receives (and a function is a type of object) into one. If the object has a toString method, then that will be called to perform said conversion.



回答4:

The sum and f functions always return the f function, you can invoke it infinite times without getting a result other than the function.

The value is only returned by the overwritten toString method of f (which actually returns a number):

console.log( sum(1)(2) ) // Function(){}
console.log( sum(1)(2).toString() ) // 3

The alert function implicitly invokes the toString method when it casts its arguments to strings.



回答5:

It doesn't work as intended in all of the cases... The problem is that .toString is expected to return a string, so string methods in provided implementation, would not work, e. g. sum(2)(3).split() will cause an error.

Although we might assume sum() result will always be expected to be a number, it might not be true in some cases and might be hard to debug, e. g. I noticed the issue when I was testing code initially written with .toString only on jsbin.com (it does split on console.log argument internally, overriding it).

Instead, .toString should look like return String(result);. Good thing that .toString (when there's no .valueOf or modern Symbol.toPrimitive) will handle primitives conversion, so code expecting a Number will work as well. The possible issue here might be "double" conversion caused by this.

Better solution might be to use either pair of .toString and .valueOf or just a single Symbol.toPrimitive if you're only targeting modern browsers.

Example using Symbol.toPrimitive:

function sum(a) {
  let result = a;

  function f(b) {
    result += b;

    return f;
  }

  f[Symbol.toPrimitive] = hint => hint === 'string' ? String(result) : result;

  return f;
}

Example using .toString and .valueOf pair.

function sum(a) {
  var result = a;

  function f(b) {
    result += b;

    return f;
  }

  // avoiding double conversion which will happen in case of .toString
  f.valueOf = function() { return result; };
  f.toString = function() { return String(result); };

  return f;
}