Usage of rest parameter and spread operator in javascript

14.8k views Asked by At

What's the usage of rest parameter that will be added in ECMAScript 6?

For example, in ECMAScript 5 you can do the following to get an array of parameters starting from the second element:

// ES 5
store('Joe', 'money');
store('Jane', 'letters', 'certificates');
function store(name) {
  var items = [].slice.call(arguments, 1); //['money'] in first case
  items.forEach(function (item) {
    vault.customer[name].push(item);
  });
}

and that will be equivalent to the following code in ECMAScript 6:

// ES 6
store('Joe', 'money');
store('Jane', 'letters', 'certificates');
function store(name, ...items) {
  items.forEach(function (item) {
    vault.customer[name].push(items)
  });
}

Is the difference between them is just syntax or there's a performance issue?

Also for spread operator (...)

//in ecmascript5
var max = Math.max.apply(null, [14, 3, 77]);
//but in ecmascript6
var max = Math.max(...[14, 3, 77]);

Is this just syntax change or performance issue?

6

There are 6 answers

0
Hazem Hagrass On

I think the main differences in rest parameter are:

  • Arguments in ECMA5 arguments is like array not an array, so array normal methods is not available, but in ECMA6 arguments is an array.
  • I think building items array in ECMA5 code example will make the code a little slower because new array is instantiated.
0
kangax On

Is the difference between them is just syntax or there's a performance issue?

Both, and more...

Rest parameters:

  1. Are a known idiom in other languages (Ruby, Python).
  2. Are esier to read and maintain (vs. slice).
  3. Are easier to understand for beginners.
  4. Can (and likely will) result in better performance, since engines can optimize.
  5. Are tool friendlier, as they can be analyzed statically.
0
Kris Kowal On

In addition to @kangax’s response, I would elaborate that performance and correctness are problematic many times the arguments object is invoked. If you pass the arguments object to another function, you pass with it the right to modify the corresponding local variables in your scope.

function foo(arg) {
    bar(arguments);
    return arg;
}

function bar(args) {
    args[0] = 20;
}

foo(10) // 20

The existence of this feature invalidates local reasoning within a function, which makes JIT optimization more difficult and introduces certain security hazards. Programs that are designed for speed must work around this problem with far more elaborate boilerplate code than the Array.prototype.slice.call(arguments) idiom, and in security contexts, the passing of arguments must be strictly disallowed.

Eliminating the need to use the arguments object to extract variadic arguments is one of the many advantages to the new syntax.

1
Thalaivar On

A rest parameter will collect individual parameters into an array when you use the dots in a function parameter definition, while the spread operator expands an array into individual parameters when using the dots in a function call.

When ever you want to collect individual parameters into an array, you would use rest operator in your function param definition.

let sum = (...numbers) => {
  let result = 0;

  numbers.forEach(function(n){
  result += n;
  });

  return result;
};

console.log(sum(1,2,3));

Dealing with arguments object inside nested function becomes really tricky, let's visit the below situation where our inner function needs access to the arguments object passed.

If our inner function filterNumbers needs access to arguments, it has to be stored above in a variable before passing it further as each function has its own arguments object, which is an array like object.

function sumOnlyNumbers() {
  var args = arguments;
  var numbers = filterNumbers();
  return numbers.reduce((sum, element) => sum + element);
    function filterNumbers() {
      return Array.prototype.filter.call(args,
        element => typeof element === 'number'
    );
  }
}
sumOnlyNumbers(1, 'Hello', 5, false);

The approach works, but it's too verbose. var args = arguments can be omitted and Array.prototype.filter.call(args) can be transformed to args.filter() using a rest parameter.

function sumOnlyNumbers(...args) {
  var numbers = filterNumbers();
  return numbers.reduce((sum, element) => sum + element);
    function filterNumbers() {
      return args.filter(element => typeof element === 'number');
    }
  }
sumOnlyNumbers(1, 'Hello', 5, false); // => 6

When ever you want to expand an array into individual parameters, you would use spread operator in your function call.

let sum = (a,b,c) => {
  return a + b + c;
};

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

In the above code, the spread operator will “spread” the array of three values across the parameters a, b, and c. The spread operator can also expand an array to create individual elements in an array literal.

var a = [4, 5, 6];
var b = [1, 2, 3, ...a, 7, 8, 9]
console.log(b);

Improved function invocation in ES6

ES5 provides .apply() method on the function object to solve this. Unfortunately this technique has 3 problems:

  • It's necessary to indicate manually the context of the function invocation
  • Is not possible to use in a constructor invocation
  • A shorter solution is more preferable

It seems irrelevant to indicate in .apply() second time the context countries making it more verbose.

let countries = ['India', 'USA'];
let otherCountries = ['China', 'Japan'];
countries.push.apply(countries, otherCountries);
console.log(countries); // => ['India', 'USA', 'China', 'Japan']

The spread operator fills the function invocation arguments with values from an array. Let's improve the above sample with a spread operator:

let countries = ['India', 'USA'];
let otherCountries = ['China', 'Japan'];
countries.push(...otherCountries);
console.log(countries); // => ['Moldova', 'Ukraine', 'USA', 'Japan']

Spread operator configures the constructor invocation arguments from an array, which is bit complicated and difficult directly when using .apply().

class Actor {
  constructor(name, country) {
  this.name = name;
  this.country = country;
  }
  getDescription() {
  return `${this.name} leads ${this.country}`;
  }
}
var details = ['RajiniKanth the Great', 'India'];
var Alexander = new Actor(...details);
console.log(Alexander.getDescription()); // => 'RajiniKanth the Great leads India'

Moreover you can combine multiple spread operators and regular arguments in the same invocation. The following example is removing from an array existing elements, then adds other array and an element:

var numbers = [1, 2];
var evenNumbers = [4, 8];
const zero = 0;
numbers.splice(0, 2, ...evenNumbers, zero);
console.log(numbers); // => [4, 8, 0]

Clone an array instance:

var words = ['Hi', 'Hello', 'Good day'];
var otherWords = [...words];
console.log(otherWords); // => ['Hi', 'Hello', 'Good day']
console.log(otherWords === words); // => false

otherWords is a clone version of words array. Notice that cloning happens only on array itself, but not on the contained elements (i.e. it's not a deep clone).

References: https://dmitripavlutin.com/how-three-dots-changed-javascript/

2
Shuvro On

Rest Operator

function sumOfNumber(...args) {
    return args.reduce((a, b) => {
        return a + b
    })
}

console.log(sumOfNumber(1, 2, 3, 4))

Spread Operator

function sumOfNumber(args) {
    console.log(...args)
}

sumOfNumber([1, 2, 3, 4])
0
Jack On

In your given example, you directly pass the object literal. Whilst still semantic, it seems un-analogous to the application of the spread operator. IMO, the spread operator's value becomes apparent when listing each parameter would determent code readability (and maintainability, when replacing traditional push, splice, concat methods).

With spread operator

let nums = [1.25,5.44,7.15,4.22,6.18,3.11];   

function add(a, b, ...nums){
  let sum = parseInt(a + b);
  nums.forEach(function(num) {
    sum += num;
  })
  return parseInt(sum);
}

console.log(add(1, 2, ...nums)); //30

ES6Fiddle demo (compiled by traceur-compiler)

Without spread operator

var nums = [1.25,5.44,7.15,4.22,6.18,3.11];   

function add(a, b, nums){
  var sum = parseInt(a + b);
  nums.forEach(function(num) {
    sum += num;
  })
  return parseInt(sum);
}

console.log(add(1, 2, nums)); //30

JSFiddle demo

In closing, I don't think that using the spread operator will improve or impede performance, it does however offer improved code readability, and arguably maintainability too. Regrettably, ES6 isn't widely implemented yet, and I'll assertively speculate that it will take sometime for browser support to ensue.

Fun fact: PHP 5.6 introduces similar functionality with its variadic functions which will make func_get_args() redundant.