Spreading arrays into arguments in JavaScript
Join the DZone community and get the full member experience.
Join For FreeSpreading
Spreading means making a function call, method call or constructor invocation and supplying the arguments via an array. In Python, one calls this unpacking. ECMAScript.next will have the spread operator (prefix ...) for this purpose. In current JavaScript, you can perform this operation via Function.prototype.apply.Example: function call. Math.max() returns the maximum among its 0 or more numeric parameters. With the spread operator, you can use it for arrays:
Math.max(...[13, 7, 30])This is the equivalent of
Math.max(13, 7, 30)In current JavaScript, you have to use apply().
> Math.max.apply(null, [13, 7, 30]) 30Explanation: An apply invocation looks as follows:
func.apply(thisValue, [param1, param2, ...])which is equivalent to
thisValue.func(param1, param2, ...)Note that func does not have to be a method of thisValue – apply temporarily turns it into one.
Example: constructor invocation. The Date constructor takes several numeric parameters and produces a date. With the spread operator, you can hand in an array.
new Date(...[2011, 11, 24]) // Christmas 2011However, apply does not work with constructors, because the new operator assumes that the trailing apply method call is the constructor. The following is a work-around:
new (Function.prototype.bind.apply( Date, [null].concat([2011, 11, 24])))What is happening here? Let’s look at the main components:
- bind: We use this method to turn Date into a function with zero parameters, by pre-filling them in. A bind invocation looks as follows:
func.bind(thisValue, [arg1], [arg2], ...)
It turns func into a new function whose implicit this parameter is thisValue and whose initial arguments are always as given. When one invokes the new function, the arguments of such an invocation are appended to what has already been provided via bind. MDN has more details. What we want to do is the following.Date.bind(null, ...[2011, 11, 24])
The first argument is null, because bind turns Date into a function that does not need a thisValue: It is only invoked as a constructor and new overrides the thisValue from bind. To simulate the above spread operator, we need the apply method. - apply: Again, we use apply to turn an array into arguments for a function call. We invoke apply on the function Function.prototype.bind, with two arguments:
- 1st argument: this has the value Date
- 2nd argument: The arguments for bind are created by prepending null to the array [2011, 11, 24]. We use the slightly more verbose concat instead of unshift, because it is non-destructive (does not change this).
Date.bind(null, 2011, 11, 24)
which is the same as the above mentioned Date.bind(null, ...[2011, 11, 24]). - new: Invoke the zero-argument function produced by bind as a constructor. By putting the operand in parentheses, we avoid apply becoming the constructor and instead use new on the result of apply.
Constructor spreading via a library method
Mozilla suggests turning the above work-around into a library method. The following is a slightly edited version of their suggestion:if (!Function.prototype.construct) { Function.prototype.construct = function(argArray) { if (! Array.isArray(argArray)) { throw new TypeError("Argument must be an array"); } var nullaryFunc = Function.prototype.bind.apply( this, [null].concat(argArray)); return new nullaryFunc(); }; }Interaction:
> Date.construct([2011, 11, 24]) Sat Dec 24 2011 00:00:00 GMT+0100 (CET)
Opinions expressed by DZone contributors are their own.
Comments