Перевод статьи «Kill .apply With The …Spread Operator», написанной Дериком Бейли.

Недавно, после обсуждения новых возможностей ES6, мне был задан вопрос о реальном применении нового оператора расширения. Этот человек хотел получить реальные примеры того, где этот оператор был бы действительно полезен.

Есть несколько вещей, которые этот оператор может делать, и одной из самых полезных я считаю отказ от необходимости использовать метод «apply» в большинстве случаев.

Массив аргументов

Пусть у вас имеется массив, который нужно передать в качестве списка аргументов функции. Когда вы передаёте этот массив без какого-либо специального кода или синтаксиса, то есть просто передавая его в качестве параметра — весь массив присваивается именованному аргументу внутри функции.

function foo(a, b, c){
  console.log("a", a);
  console.log("b", b);
  console.log("c", c);
}

var arr = [1, 2, 3];

foo(arr);
// => a [1, 2, 3]
// => b undefined
// => c undefined

Но что делать, когда вы хотите, чтобы значения массива применялись к каждому индивидуально-именованному аргументу в функции? В прошлом это можно было сделать, используя метод «apply» для функции.

function foo(a, b, c){
  console.log("a", a);
  console.log("b", b);
  console.log("c", c);
}

var arr = [1, 2, 3];

foo.apply(undefined, arr);
// => a 1
// => b 2
// => c 3

Этот метод разобьёт ваш массив на части, которые в последствии будут переданы в каждый аргумент функции соответственно. Это действительно полезный инструмент, но с ES6 и оператором расширения, это становится ещё проще.

Использование оператора расширения

ES6 добавляет новую форму синтаксиса, где вы можете передать массив функции, и его содержимое «разворачивается» в её аргументы. Это делается с помощью синтаксиса ...array. Такой синтаксис называется оператором расширения.

Во многих случаях, этим оператором можно заменить вызов метода«apply» у функции.

function foo(a, b, c){
  console.log("a", a);
  console.log("b", b);
  console.log("c", c);
}

var arr = [1, 2, 3];

foo(...arr);
// => a 1
// => b 2
// => c 3

Эти два фрагмента кода (предыдущая версия кода с методом «apply» и эта) практически эквивалентны — во многих случаях они эквиваленты и в результате. Но есть важная разница, которую нужно понимать: «this».

this в apply и операторе расширения

Вызывая «apply», первый параметр всегда используется для установления контекста функции. Это значение может вовсе отсутствовать, например, когда «this» не используется внутри функции, то есть равен undefined.

Если внутри вашей функции используется «this», то вы будете нарываться на проблему с undefined в качестве первого параметра, так как «this» будет неопределенным.

var obj = {
  a: 1, 
  b: 2, 
  c: 3,
  
  foo: function(d, e, f){
    console.log("a d", this.a, d);
    console.log("b e", this.b, e);
    console.log("c f", this.c, f);
  }
};

var arr = [4, 5, 6];

obj.foo.apply(undefined, arr); // => Ошибка!!!!!!

Чтобы исправить это, вам нужно передать ожидаемый «this» в качестве первого параметра.

var obj = {
  a: 1, 
  b: 2, 
  c: 3,
  
  foo: function(d, e, f){
    console.log("a d", this.a, d);
    console.log("b e", this.b, e);
    console.log("c f", this.c, f);
  }
};

var arr = [4, 5, 6];

obj.foo.apply(obj, arr); // => ... ожидаемый результат "a d 1 4", etc

При использовании оператора расширения вам не нужно передавать «this», так как оператор расширения не требует явного указания контекста. Ваша функция может быть вызвана с массивом как списком параметров, и значение «this» здесь подразумевается механизмом вызова функции.

var obj = {
  a: 1, 
  b: 2, 
  c: 3,
  
  foo: function(d, e, f){
    console.log("a d", this.a, d);
    console.log("b e", this.b, e);
    console.log("c f", this.c, f);
  }
};

var arr = [4, 5, 6];

obj.foo(...arr); // => ... ожидаемый результат "a d 1 4", etc

В этой версии нет потребности указывать значение для «this», разумеется, если неявное «this» — это то, чего вы хотите. Оператор расширения позволяет obj.foo() сохранить obj как this при вызове внутри функции.

Оператор расширения не полностью убивает .apply

Польза оператора расширения распространяется далеко за пределы применения массивов в аргументах функции — но это одно из самых важных преимуществ, которое это нововведение предоставляет. С помощью этого синтаксиса можно пропустить вызов «apply» во многих случаях. Это несильно уменьшит код, но что более важно — это снижает необходимый уровень требуемых знаний для решения сложностей с «this».

Однако, оператор расширения не всегда устанавливает «this». Он делает это лишь тогда, когда используется механизм вызова функций. Если вы должны явно установить значение «this», то вызов «apply» все еще обязателен. В итоге, даже с этим ограничением оператор расширения может уменьшить объем кода, который требуется для того, чтобы вызвать функцию с элементами массива в виде списка параметров.

Примечание от переводчика

Более полное описание возможностей оператора расширения можно найти в переводе статьи «ES6 Spread and Butter in Depth» от команды CSS-LIVE.