Что нового в ES2018: функции Rest/Spread

внешний интерфейс JavaScript ECMAScript 8

Предложение Sebastian Markbåge по свойствам Rest/Spread Properties состоит из двух частей:

  • Оператор rest (...) для деструктуризации объекта. В настоящее время этот оператор можно использовать только при деструктуризации массива и определении параметров.
  • Оператор распространения (...) в литералах объектов. В настоящее время этот оператор можно использовать только в литералах массива и в методах функций.

оператор rest (...) в деструктурировании объекта

В режиме деструктуризации объекта оператор rest копирует в свой операнд все перечисляемые собственные свойства источника деструктуризации, кроме тех, которые уже указаны в литерале объекта.

const obj = {foo: 1, bar: 2, baz: 3};
const {foo, ...rest} = obj;
// Same as:
// const foo = 1;
// const rest = {bar: 2, baz: 3};

Если вы используете деструктурирование объекта для обработки именованных параметров, оператор rest позволяет собрать все оставшиеся параметры:

function func({param1, param2, ...rest}) { // rest operator
    console.log('All parameters: ',{param1, param2, ...rest}); // spread operator
    return param1 + param2;
}

Синтаксические ограничения

На верхнем уровне каждого литерала объекта оператор rest может использоваться не более одного раза и должен появляться только в конце:

const {...rest, foo} = obj; // SyntaxError
const {foo, ...rest1, ...rest2} = obj; // SyntaxError

Если это вложенная структура, вы можете использовать оператор rest несколько раз:

const obj = {
    foo: {
        a: 1,
        b: 2,
        c: 3,
    },
    bar: 4,
    baz: 5,
};

const {foo: {a, ...rest1}, ...rest2} = obj;

// Same as:
// const a = 1;
// const rest1 = {b: 2, c: 3};
// const rest2 = {bar: 4, baz: 5};

оператор расширения в литералах объектов

Внутри литерала объекта оператор расширения вставляет все перечисляемые собственные свойства своего операнда в объект, созданный литералом:

> const obj = {foo: 1, bar: 2, baz: 3};
> {...obj, qux: 4}
{ foo: 1, bar: 2, baz: 3, qux: 4 }

На что следует обратить внимание, так это на проблему порядка, хотя ключи свойств не конфликтуют, поскольку объект отслеживает порядок вставки:

> {qux: 4, ...obj}
{ qux: 4, foo: 1, bar: 2, baz: 3 }

Если ключ конфликтует, последний перезапишет прежнее свойство:

> const obj = {foo: 1, bar: 2, baz: 3};
> {...obj, foo: true}
{ foo: true, bar: 2, baz: 3 }
> {foo: true, ...obj}
{ foo: 1, bar: 2, baz: 3 }

Сценарии использования оператора распространения объекта

В этом разделе мы рассмотрим сценарии использования оператора спреда. Я также реализую его с помощью Object.assign() , который очень похож на оператор распространения (позже мы сравним их более подробно).

копировать объект

Скопируйте перечисляемые собственные свойства объекта obj:

const clone1 = {...obj};
const clone2 = Object.assign({}, obj);

Clone Prototype объекта является объектом. Прототип, это прототип по умолчанию для всех объектов, созданных объектом литералом:

> Object.getPrototypeOf(clone1) === Object.prototype
true
> Object.getPrototypeOf(clone2) === Object.prototype
true
> Object.getPrototypeOf({}) === Object.prototype
true

Скопируйте объект obj, включая его прототип:

const clone1 = {__proto__: Object.getPrototypeOf(obj), ...obj};
const clone2 = Object.assign(
    Object.create(Object.getPrototypeOf(obj)), obj);

Обратите внимание, что, как правило, внутри литералов объектовprotoЭто просто функция, встроенная в браузер, а не в движок JavaScript.

точная копия объекта

Иногда вам нужно честно скопировать все собственные свойства объекта (свойства) и свойства (доступные для записи, перечисления,...), включая геттеры и сеттеры. На данный момент Object.assign() и оператор распространения бесполезны. Вам нужно использовать дескриптор свойства (property descriptors):

const clone1 = Object.defineProperties({},
    Object.getOwnPropertyDescriptors(obj));

Если вы также хотите сохранить прототип obj, вы можете использоватьObject.create():

const clone2 = Object.create(
    Object.getPrototypeOf(obj),
    Object.getOwnPropertyDescriptors(obj));

Представлено в разделе «Изучение ES2016 и ES2017».Object.getOwnPropertyDescriptors()

Подводный камень: всегда поверхностное копирование

Все способы копирования объектов, которые мы видели ранее, являются поверхностными копиями: если исходное значение свойства является объектом, скопированный объект будет указывать на тот же объект, он не будет (рекурсивно, глубоко) копировать себя:

const original = { prop: {} };
const clone = Object.assign({}, original);

console.log(original.prop === clone.prop); // true
original.prop.foo = 'abc';
console.log(clone.prop.foo); // abc

Другие сценарии использования

Объединить два объекта obj1 и obj2:

const merged = {...obj1, ...obj2};
const merged = Object.assign({}, obj1, obj2);

Заполнить пользовательские данные значениями по умолчанию

const DEFAULTS = {foo: 'a', bar: 'b'};
const userData = {foo: 1};

const data = {...DEFAULTS, ...userData};
const data = Object.assign({}, DEFAULTS, userData);
    // {foo: 1, bar: 'b'}

Безопасно обновите свойство foo:

const obj = {foo: 'a', bar: 'b'};
const obj2 = {...obj, foo: 1};
const obj2 = Object.assign({}, obj, {foo: 1});
    // {foo: 1, bar: 'b'}

Укажите значения по умолчанию для свойств foo и bar:

const userData = {foo: 1};
const data = {foo: 'a', bar: 'b', ...userData};
const data = Object.assign({}, {foo:'a', bar:'b'}, userData);
    // {foo: 1, bar: 'b'}

Развернуть объект VS Object.assign()

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

Два способа использования Object.assign()

Object.assign() можно использовать двумя способами: Первый, с деструктивным (модификация существующих объектов):

Object.assign(target, source1, source2);

Целевой объект здесь изменен; в него копируются Source1 и Source2. Во-вторых, неразрушающий (существующие объекты не будут изменены):

const result = Object.assign({}, source1, source2);

Новый объект создается путем копирования source1 и source2 в пустой объект. Наконец, этот новый объект возвращается и присваивается результату. Оператор распространения аналогичен второму способу Object.assign() . Далее, давайте посмотрим на сходства и различия между ними.

Оба считывают значение через оператор "получить"

Оба используют оператор «получить» для чтения значений свойств исходного объекта перед записью объекта. Этот процесс преобразует геттер в обычное свойство данных. Давайте посмотрим пример:

const original = {
    get foo() {
        return 123;
    }
};

у оригинала есть геттер foo (его дескриптор свойства имеет свойства get и set)

> Object.getOwnPropertyDescriptor(original, 'foo')
{ get: [Function: foo],
  set: undefined,
  enumerable: true,
  configurable: true }

Но в clone1 и clone2 он копирует, foo является обычным свойством данных (дескриптор свойства имеет значение и доступные для записи свойства):

> const clone1 = {...original};
> Object.getOwnPropertyDescriptor(clone1, 'foo')
{ value: 123,
  writable: true,
  enumerable: true,
  configurable: true }

> const clone2 = Object.assign({}, original);
> Object.getOwnPropertyDescriptor(clone2, 'foo')
{ value: 123,
  writable: true,
  enumerable: true,
  configurable: true }

распространение определяет свойства, Object.assign() устанавливает свойства

Оператор распространения определяет новые свойства целевого объекта, в то время как Object.assign() использует оператор «установить» для создания свойств. Это приводит к двум результатам:

Целевой объект имеет сеттер

Во-первых, Object.assign() запускает сеттеры, а спред — нет:

Object.defineProperty(Object.prototype, 'foo', {
    set(value) {
        console.log('SET', value);
    },
});
const obj = {foo: 123};

Приведенный выше фрагмент кода устанавливает сеттер foo, который наследуется всеми обычными объектами. Если мы скопируем obj через Object.assign(), сработает унаследованный сеттер:

> Object.assign({}, obj)
SET 123
{}

И распространение не:

> { ...obj }
{ foo: 123 }

Object.assign() также запускает собственный сеттер при копировании, и здесь не происходит перезаписи.

Целевой объект имеет свойства только для чтения

Во-вторых, вы можете запретить Object.assign() создавать ваши собственные свойства, унаследовав свойства только для чтения, но это невозможно с распространением:

Object.defineProperty(Object.prototype, 'bar', {
    writable: false,
    value: 'abc',
});

Приведенный выше код устанавливает панель свойств, доступную только для чтения, которая наследуется всеми обычными объектами. Таким образом, вы больше не сможете использовать операторы присваивания для создания собственной панели свойств (в строгом режиме выдается исключение, а в свободном режиме происходит сбой):

> const tmp = {};
> tmp.bar = 123;
TypeError: Cannot assign to read only property 'bar'

В следующем коде мы успешно создаем панель свойств, используя литерал объекта. Поскольку литерал объекта не устанавливает свойства, он просто определяет их:

const obj = {bar: 123};

Однако Object.assign() использует оператор присваивания для создания свойств, поэтому obj нельзя скопировать:

> Object.assign({}, obj)
TypeError: Cannot assign to read only property 'bar'

С копированием через оператор спреда проблем нет:

> { ...obj }
{ bar: 123 }

И распространение, и Object.assign() копируют только собственные перечисляемые свойства

Оба они игнорируют все унаследованные свойства и неперечислимые собственные свойства. Объект obj наследует перечисляемое свойство от proto и имеет два собственных свойства:

const proto = {
    inheritedEnumerable: 1,
};
const obj = Object.create(proto, {
    ownEnumerable: {
        value: 2,
        enumerable: true,
    },
    ownNonEnumerable: {
        value: 3,
        enumerable: false,
    },
});

Если obj скопирован, результат будет иметь только свойство ownEnumerable. Свойства inheritedEnumerable и ownNonEnumerable не копируются:

> {...obj}
{ ownEnumerable: 2 }
> Object.assign({}, obj)
{ ownEnumerable: 2 }

Оригинал: http://exploringjs.com/es2018-es2019/ch_rest-spread-properties.html