JavaScript ES6 принес нам множество функциональных улучшений и приятный синтаксический сахар. Среди них одними из самых элегантных и, возможно, моими любимыми должны быть эти три маленькие точки , известные как «оператор расширения» и «параметр остатка» (в зависимости от синтаксического контекста, но об этом позже).

Таким образом, оператор распространения ES6 делает… Что ж, именно то, что он обещает, — распределяет итерируемый объект (массив, строку или объект*) на отдельные элементы. Параметр rest, с другой стороны, позволяет нам представить неопределенное количество аргументов в виде массива.

*официальная поддержка использования объектов была добавлена ​​в ES9 (конец 2018 г.)

Тем не менее, простое расширение и представление наборов данных и параметров — это всего лишь два основных лезвия функционального многофункционального инструмента, который предоставляют нам эти три маленькие волшебные точки (иначе это было бы довольно бессмысленным описанием, верно?).

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

Прежде чем мы углубимся в настоящие крутые и блестящие функции, которые предлагают оператор распространения и параметр rest, давайте сначала кратко проясним распространенное заблуждение, которое окружает эти два параметра, а именно:

«Операторы расширения и остальные параметры — это одно и то же».

Это неверно.

Несмотря на то, что визуально они представлены одними и теми же тремя точками (), эти две точки на самом деле выполняют «противоположные» задачи. Повторим еще раз: в то время как оператор расширения расширяет массив/итерацию на отдельные элементы, параметр rest сжимает отдельные элементы обратно в массив.

Хотя кажется, что их легко спутать, на самом деле довольно легко отличить их друг от друга. Все, что вам нужно помнить, чтобы отличить их друг от друга, это то, что параметр rest может использоваться ТОЛЬКО как последний элемент в параметрах функции.

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

РАЗДЕРЖАТЬ СТРОКИ

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

Расширение строки с помощью Array.prototype.split('')

const meow = "kitty-cat";
const spreadCat = meow.split('')
console.log(spreadCat)
// logs [ 'k', 'i', 't', 't', 'y', '-', 'c', 'a', 't' ]

Расширение строки с помощью оператора распространения

const meow = "kitty-cat";
const spreadCat = [...meow]
console.log(spreadCat)
// logs [ 'k', 'i', 't', 't', 'y', '-', 'c', 'a', 't' ]

КОПИРОВАТЬ МАССИВЫ/ОБЪЕКТЫ

Одна из наиболее часто повторяющихся задач в JavaScript — создание копий литералов массивов и объектов. Оператор спреда заметно укорачивает для нас синтаксис.

Создание копии массива с помощью Array.prototype.slice()

const kittens = ['persian','bengal','siamese'];
const newLitter = kittens.slice();
console.log(newLitter)
// logs [ 'persian', 'bengal', 'siamese' ]

Создание копии массива с помощью оператора распространения

const kittens = ['persian','bengal','siamese'];
const newLitter = [...kittens];
console.log(newLitter)
// logs [ 'persian', 'bengal', 'siamese' ]

Создание копии объекта с помощью Object.assign()

const cat = {
  name: "Mittens",
  legNum: 4,
  hasTail: true
};
const catClone = Object.assign({}, cat);
console.log(catClone)
// logs { name: 'Mittens', legNum: 4, hasTail: true }

Создание копии объекта с оператором распространения

const cat = {
  name: "Mittens",
  legNum: 4,
  hasTail: true
};
const catClone = {...cat};
console.log(catClone)
// { name: 'Mittens', legNum: 4, hasTail: true }

ОБЪЕДИНИТЬ МАССИВЫ

Не то, чтобы конкатенация когда-либо была действительно сложной, но оператор распространения позволяет нам сделать это в сжатой форме, даже при объединении нескольких массивов.

Объединение с Array.prototype.concat()

const cats1 = ['Mittens', 'Stewart', 'Tom'];
const cats2 = ['Snowball', 'Felix', 'Whiskers'];
const catFriends = cats1.concat(cats2);
console.log(catFriends)
// [ 'Mittens', 'Stewart', 'Tom', 'Snowball', 'Felix', 'Whiskers' ]

Конкатенация с оператором распространения

const cats1 = ['Mittens', 'Stewart', 'Tom'];
const cats2 = ['Snowball', 'Felix', 'Whiskers'];
const catFriends = [...cats1, ...cats2];
console.log(catFriends)
// [ 'Mittens', 'Stewart', 'Tom', 'Snowball', 'Felix', 'Whiskers' ]

ОБЪЕДИНИТЕ МАССИВЫ / ОБЪЕКТЫ

Мы можем использовать .push(), .unshift() и .splice(), чтобы легко комбинировать элементы с массивами, но свободное объединение нескольких массивов до ES6 никогда не было простым процессом. Хотя .concat() выполняет свою работу, он дает мало свободы. Оператор распространения не только позволяет нам комбинировать массивы в любом порядке, но и делает это без каких-либо хлопот.

Объединение массивов с помощью оператора распространения

const cats1 = ['Mittens', 'Stewart'];
const cats2 = ['Snowball', 'Felix'];
const cats3 = ['Hobbs', 'Scratchy']
const catParty = [...cats1, 'Tom', 'Whiskers', ...cats2, 'Mimi', ...cats3]
console.log(catParty)
// logs:
[ 'Mittens', 'Stewart', 'Tom', 'Whiskers', 'Snowball', 'Felix', 'Mimi', 'Hobbs', 'Scratchy' ]

Объекты, будучи неупорядоченными, было легче комбинировать, чем массивы с помощью Object.assign(), но оператор распространения все же облегчает задачу.

Объединение объектов с оператором Object.assign()/spread

const catProps = {
  legNum: 4,
  tailNum: 1,
  hasClaws: true,
}
const catProps2 = {
  favFood: "fish",
  sound: "meow",
  likesWater: false
}
const cat = Object.assign(catProps, catProps2)
const catSpread = {...catProps, ...catProps2}
console.log(cat)
console.log(catSpread)
// logs (same result for both methods):
{ legNum: 4,
  tailNum: 1,
  hasClaws: true,
  favFood: 'fish',
  sound: 'meow',
  likesWater: false }

ВЫЗОВ ФУНКЦИЙ ВМЕСТО «.apply()»

Когда мы хотим передать массив/список аргументов функции, общепринято использовать Function.prototype.apply(). Это работает, но опять же, кажется неинтуитивным и оставляет место для неправильного использования теми, кто не знаком с методом. ES6 позволяет нам использовать оператор распространения в качестве простой замены (в большинстве случаев), где мы обычно использовали бы .apply().

Вызов функции с помощью Function.prototype.apply()

function printCats (a, b, c) {
  console.log(a + ", " + b + ", and " + c)
};
const cats = ["Felix", "Tom", "Mimi"];
printCats.apply(null, cats);
// logs "Felix, Tom, and Mimi"

Вызов функции с оператором спреда

const printCats = (a, b, c) => {
  console.log(`${a}, ${b}, and ${c}`)
};
const cats = ["Felix", "Tom", "Mimi"];
printCats(...cats);
// logs "Felix, Tom, and Mimi"

Но подождите! А как насчет «this»?

По умолчанию использование одного оператора расширения для вызова функции с массивом эквивалентно передаче «undefined» в качестве первого параметра .apply() (т. е. <object>.apply(undefined, <array>)). Это может быть проблемой, когда нам нужно предоставить контекст для this.

Однако все, что нам нужно сделать, чтобы исправить ситуацию, это поместить объект, который мы хотим связать, this слева от точки во время вызова… звучит знакомо? 😏

const catObj = {
  favFood: "fish",
  sound: "meow",
  fur: "soft",
  
  describeCat: function(a, b, c){
    //note: cannot use ES6 arrow function due to lexical scoping
    console.log(`I like to ${a} ${this.favFood}.`);
    console.log(`The sound I ${b} is ${this.sound}.`);
    console.log(`${c} my ${this.fur} fur.`);
  }
};
const actions = ["eat", "make", "Stroke"];
catObj.describeCat(...actions);
//this is equivalent to: catObj.describeCat.apply(catObj, actions);
//logs:
I like to eat fish.
The sound I make is meow.
Stroke my soft fur.

ПРИМЕЧАНИЕ. Хотя оператор распространения позволяет нам устранить сложность и заменить .apply() в большинстве случаев, мы не можем использовать его для установки this каким-либо образом, кроме как в контексте вызова функции. Другими словами, если значение this необходимо установить явно, использование .apply() по-прежнему остается правильным.

БОНУС: МАТЕМАТИКА

Помимо специально разработанных применений, которые мы только что рассмотрели выше, оператор распространения также может быть полезен во многих других творческих целях. Одна из таких ситуаций — передача массива чисел в Math.prototype методов.

Вот быстрый пример использования Math.max()

const catsAge = [2, 4, 7, 1];
const oldestAge = Math.max(...catsAge);
console.log(oldestAge);
//logs 7
//if we were to do this without the spread operator, we would need to implement an extra step to convert the catsAge array to individual numbers (i.e. with Array.values(catsAge) or similar)

Поздравляю, что дошли до конца! Теперь идите и используйте оператор распространения в своем коде! Вернитесь на следующей неделе, чтобы увидеть вторую часть нашего исследования остальных параметров и чудесного мира реструктуризации! 😺