Распространение объекта против Object.assign

Допустим, у меня есть options переменная, и я хочу установить значение по умолчанию.

Какова польза / недостаток этих двух альтернатив?

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

options = {...optionsDefault, ...options};

Или используя Object.assign

options = Object.assign({}, optionsDefault, options);

Это то, что заставило меня задуматься.

16 ответов

Решение

Это не обязательно является исчерпывающим.

Синтаксис распространения

options = {...optionsDefault, ...options};

Преимущества:

  • Если вы создаете код для выполнения в средах без встроенной поддержки, вы можете просто скомпилировать этот синтаксис (в отличие от использования полифилла). (С Вавилоном, например.)

  • Менее многословно.

Недостатки:

  • Предложение не стандартизировано. (Подумайте, что бы вы сделали, если бы написали это сейчас, а оно не стандартизировалось.)

  • Буквально, не динамично.


Object.assign()

options = Object.assign({}, optionsDefault, options);

Преимущества:

  • Стандартизован.

  • Динамический. Пример:

    var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
    options = Object.assign.apply(Object, [{}].concat(sources));
    
  • Более многословно.

Недостатки:

  • Если вы разрабатываете код для выполнения в средах без встроенной поддержки, вам необходимо выполнить полифилл.

Это то, что заставило меня задуматься.

Это не имеет прямого отношения к тому, что вы спрашиваете. Этот код не использовал Object.assign(), он использовал код пользователя (object-assign) это делает то же самое. Похоже, они компилируют этот код с Babel (и связывают его с Webpack), о чем я говорил: синтаксис, который вы можете просто скомпилировать. Очевидно, они предпочли, чтобы object-assign как зависимость, которая вошла бы в их сборку.

Для эталонного объекта отдых / разворот завершается в ECMAScript 2018 как этап 4. Предложение можно найти здесь.

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

Стоит помнить, что в остальном объект rest/spread 1:1 отображается на Object.assign() и действует иначе, чем массив (повторяемый). Например, при распространении массива растягиваются нулевые значения. Однако использование нулевых значений распространения объекта молча распространяется на ничто.

Пример массива (Iterable) Spread

const x = [1, 2, null , 3];
const y = [...x, 4, 5];

console.log(y); // [1, 2, null, 3, 4, 5];

Пример распространения объекта

const x = null;
const y = {a: 1, b: 2};
const z = {...x, ...y};

console.log(z); //{a: 1, b: 2}

Это согласуется с тем, как Object.assign() будет работать, и оба молча исключают нулевое значение без ошибок.

const x = null;
const y = {a: 1, b: 2};
const z = Object.assign({}, x, y);

console.log(z); //{a: 1, b: 2}

Я думаю, что одно большое различие между оператором спреда и Object.assign Похоже, что в текущих ответах не упоминается, что оператор распространения не будет сохранять прототип без изменений. Если вы хотите добавить свойства к объекту и не хотите изменять то, чем он является, то вам придется использовать Object.assign, Пример ниже должен продемонстрировать это:

const error = new Error();
console.error instanceof Error; // true

const errorExtendedUsingSpread = {
  ...error,
  ...{
    someValue: true
  }
};
errorExtendedUsingSpread instanceof Error; // false

const errorExtendedUsingAssign = Object.assign(error, {
  someValue: true
});
errorExtendedUsingAssign instanceof Error; // true

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

Разница в том, что Object.assignизменяет объект на месте, а оператор распространения ( ...) создает совершенно новый объект, и это нарушит равенство ссылок на объекты.

Сначала давайте посмотрим на эффект, а потом я приведу реальный пример того, как важно понимать эту фундаментальную разницу.

Сначала воспользуемся Object.assign :

      // Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using Object.assign, adding a new `foo` key with `bar` value;
Object.assign(parentObject.childObject, { foo: 'bar' });

// childObject is still the same object in memory, it was changed IN PLACE.
parentObject.childObject === childObject
// true

Теперь то же упражнение с оператором распространения:

      // Let's create a new object, that contains a child object;
const parentObject = { childObject: { hello: 'world '} };

// Let's get a reference to the child object;
const childObject = parentObject.childObject;

// Let's change the child object using the spread operator;
parentObject.childObject = {
  ...parentObject.childObject,
  foo: 'bar',
}

// They are not the same object in memory anymore!
parentObject.childObject === childObject;
// false

Легко увидеть, что происходит, потому что на parentObject.childObject = {...}мы четко присваиваем значение ключа в parentObjectк совершенно новому объектному литералу , и тот факт, что он составлен старым childObjectсодержание не имеет значения. Это новый объект.

И если вы полагаете, что на практике это не имеет значения, позвольте мне показать реальный сценарий того, насколько важно это понимать.

В очень большом приложении Vue.js мы начали замечать большую медлительность при вводе имени клиента в поле ввода.

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

Этого не ожидалось, поскольку имя заказчика вообще не использовалось в этих вычисляемых функциях. Использовались только другие данные о клиентах (например, возраст, пол). Что происходит? Почему vue переоценивал все эти вычисляемые функции, когда имя клиента менялось?

Ну, у нас был магазин Vuex, который делал это:

      mutations: {
  setCustomer(state, payload) {
    state.customer = { ...state.customer, ...payload };
  }

И наши вычисления были такими:

      veryExpensiveComputed() {
   const customerAge = this.$store.state.customer.age;
}

Итак, вуаля! Когда имя клиента изменилось, мутация Vuex фактически полностью изменила его на новый объект; и поскольку вычисляемый объект полагался на этот объект для получения возраста клиента, Vue рассчитывал на этот очень конкретный экземпляр объекта как на зависимость, и когда он был изменен на новый объект (сбой ===тест на равенство объектов), Vue решил, что пришло время перезапустить вычисляемую функцию.

Исправление? Используйте Object.assign, чтобы не отбрасывать предыдущий объект, а менять его на месте...

      mutations: {
  setCustomer(state, payload) {
    Object.assign(state.customer, payload);
  }

Кстати, если вы работаете в Vue2, вам не следует использовать Object.assign, потому что Vue2 не может напрямую отслеживать изменения этих объектов, но применяется та же логика, просто используя Vue.set вместо Object.assign:

      mutations: {
  setCustomer(state, payload) {
    Object.keys(payload).forEach(key => {
      Vue.set(state.customer, key, payload[key])
    })
  }

ПРИМЕЧАНИЕ: распространение не просто синтаксический сахар вокруг Object.assign. Они работают по-разному за кадром.

Object.assign применяет сеттеры к новому объекту, а Spread - нет. Кроме того, объект должен быть итеративным.

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

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

Назначить Назначить несколько противоположно копировать. Assign сгенерирует установщик, который присваивает значение переменной экземпляра напрямую, а не копирует или сохраняет его. При вызове метода получения назначенного свойства он возвращает ссылку на фактические данные.

Я хотел бы обобщить состояние функции ES "слияние распространенных объектов" в браузерах и в экосистеме с помощью инструментов.

спекуляция

Браузеры: в Chrome, в SF, Firefox скоро (версия 60, IIUC)

  • Поддержка браузером "распространяемых свойств", поставляемых в Chrome 60, включая этот сценарий.
  • Поддержка этого сценария НЕ работает в текущем Firefox (59), но работает в моем Firefox Developer Edition. Поэтому я считаю, что он будет выпущен в Firefox 60.
  • Safari: не тестировался, но Kangax говорит, что он работает в Desktop Safari 11.1, но не SF 11
  • iOS Safari: не teseted, но Kangax говорит, что он работает в iOS 11.3, но не в iOS 11
  • еще не в краю

Инструменты: Узел 8.7, TS 2.1

связи

Пример кода (удваивается как тест на совместимость)

var x = { a: 1, b: 2 };
var y = { c: 3, d: 4, a: 5 };
var z = {...x, ...y};
console.log(z); // { a: 5, b: 2, c: 3, d: 4 }

Опять же: на момент написания этого примера этот пример работал без перехода в Chrome (60+), Firefox Developer Edition (предварительный просмотр Firefox 60) и Node (8.7+).

Зачем отвечать?

Я пишу это через 2,5 года после первоначального вопроса. Но у меня был тот же самый вопрос, и это то, куда Google послал меня. Я раб миссии SO по улучшению длинного хвоста.

Поскольку это расширение синтаксиса "разброса по массивам", мне было очень трудно его найти в Google, и его трудно найти в таблицах совместимости. Самое близкое, что я могу найти - это Kangax "свойство-разброс", но в этом тесте нет двух спредов в одном выражении (не слияние). Кроме того, имя на страницах предложений / проектов / браузера использует слово "распространение свойств", но мне кажется, что это был "первый принцип", к которому пришло сообщество после предложений использовать синтаксис распространения для "слияния объектов". (Это может объяснить, почему в Google так сложно.) Поэтому я документирую свои находки здесь, чтобы другие могли просматривать, обновлять и компилировать ссылки об этой конкретной функции. Я надеюсь, что это завоевывает популярность. Пожалуйста, помогите распространять новости о его посадке в спецификации и в браузерах.

Наконец, я бы добавил эту информацию в качестве комментария, но я не мог редактировать их, не нарушив первоначальные намерения авторов. В частности, я не могу редактировать комментарий @ChillyPenguin без потери намерения исправить @RichardSchulte. Но спустя годы Ричард оказался прав (на мой взгляд). Поэтому я пишу этот ответ вместо этого, надеясь, что со временем он получит поддержку старых ответов (может потребоваться годы, но в конце концов это и есть эффект длинного хвоста).

Оператор распространения объекта (...) не работает в браузерах, потому что он пока не является частью какой-либо спецификации ES, а является лишь предложением. Единственный вариант - это скомпилировать его с помощью Babel (или чего-то подобного).

Как видите, это всего лишь синтаксический сахар над Object.assign({}).

Насколько я вижу, это важные различия.

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

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

Рассмотрим этот код:

// Babel wont touch this really, it will simply fail if Object.assign() is not supported in browser.
const objAss = { message: 'Hello you!' };
const newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

// Babel will transpile with use to a helper function that first attempts to use Object.assign() and then falls back.
const objSpread = { message: 'Hello you!' };
const newObjSpread = {...objSpread, dev: true };
console.log(newObjSpread);

Оба они дают одинаковый результат.

Вот вывод из Babel в ES5:

var objAss = { message: 'Hello you!' };
var newObjAss = Object.assign(objAss, { dev: true });
console.log(newObjAss);

var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };

var objSpread = { message: 'Hello you!' };
var newObjSpread = _extends({}, objSpread, { dev: true });
console.log(newObjSpread);

Это мое понимание до сих пор. Object.assign() на самом деле стандартизирован, где, как объект распространения ... еще нет. Единственная проблема - поддержка браузера для первого и в будущем, второго тоже.

Играть с кодом здесь

Надеюсь это поможет.

Слишком много неправильных ответов...

Я сделал поиск и нашел много дезинформации по этому поводу.

Краткое содержание

  1. Ни то, ни другое не быстрее. Это зависит.
  2. почти всегда быстрее, если не учитывать побочные эффекты/мутацию объекта.
  3. Помимо производительности, как правило, нет никаких преимуществ или недостатков в использовании, пока вы не достигнете крайних случаев, таких как копирование объектов, содержащих геттеры/сеттеры, или свойства только для чтения. Подробнее читайте здесь.

Производительность

Будь или...spreadбыстрее, это зависит от того, что вы пытаетесь объединить, и используемой среды выполнения (время от времени происходят реализации и оптимизации). Для мелких объектов это не имеет значения.

Для больших объектов обычно лучше. Особенно, если вам не нужно заботиться о побочных эффектах, выигрыш в скорости достигается за счет экономии времени за счет простого добавления свойств к первому объекту, а не копирования из двух и создания совершенно нового. Видеть:

      async function retrieveAndCombine() {
    const bigPayload = await retrieveData()
    const smallPayload = await retrieveData2()
    
    // only the properties of smallPayload is iterated through
    // whereas bigPayload is mutated.
    return Object.assign(bigPayload, smallPayload)
}

Если побочные эффекты беспокоят

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

В приведенном ниже примере будут видоизменены, что является плохой идеей, если другие функции/объекты за пределамиretrieveAndCombineзависит отbigPayload. В этом случае вы можете поменять местами аргументы, переданные вObject.assign, или просто используйте{}в качестве первого аргумента для создания нового объекта:

      async function retrieveAndCombine(bigPayload) {
    const smallPayload = await retrieveData2()

    // this is slightly more efficient
    return Object.assign(smallPayload, bigPayload)

    // this is still okay assuming `smallPayload` only has a few properties
    return Object.assign({}, smallPayload, bigPayload)
}

Тест

Убедитесь сами, попробуйте фрагмент кода ниже. Примечание. Для запуска требуется некоторое время.

Способы (1) создания мелких копий объектов и (2) объединения нескольких объектов в один объект сильно изменились в период с 2014 по 2018 год.

Изложенные ниже подходы стали доступны и широко использовались в разное время. Этот ответ дает некоторую историческую перспективу и не является исчерпывающим.

  • Без какой-либо помощи библиотек или современного синтаксиса вы бы использовали for-in петли, например

            var mergedOptions = {}
    for (var key in defaultOptions) {
      mergedOptions[key] = defaultOptions[key]
    }
    for (var key in options) {
      mergedOptions[key] = options[key]
    }
    
    options = mergedOptions
    

2006 г.

  • jQuery 1.0 имеет jQuery.extend():

            options = $.extend({}, defaultOptions, options)
    

2010 г.

2014 г.

2015 г.

  • Object.assignподдерживается Chrome (45), Firefox (34) и Node.js (4) . Однако для более старых сред выполнения требуется Polyfill.

            options = Object.assign({}, defaultOptions, options)
    
  • Предложение Object Rest / Spread Properties достигает стадии 2.

2016 г.

  • Синтаксис Object Rest / Spread Properties не был включен в ES2016, но предложение достигает стадии 3.

2017 г.

  • Синтаксис Object Rest / Spread Properties не был включен в ES2017, но его можно использовать в Chrome (60), Firefox (55) и Node.js (8.3) . Однако для более старых сред выполнения требуется некоторая транспиляция.

            options = { ...defaultOptions, ...options }
    

2018 г.

  • Предложение Object Rest / Spread Properties достигает стадии 4, и его синтаксис включен в стандарт ES2018.

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

var obj1 = { a: 1,  b: { b1: 1, b2: 'b2value', b3: 'b3value' } };

// overwrite parts of b key
var obj2 = {
      b: {
        ...obj1.b,
        b1: 2
      }
};
var res2 = Object.assign({}, obj1, obj2); // b2,b3 keys still exist
document.write('res2: ', JSON.stringify (res2), '<br>');
// Output:
// res2: {"a":1,"b":{"b1":2,"b2":"b2value","b3":"b3value"}}  // NOTE: b2,b3 still exists

// overwrite whole of b key
var obj3 = {
      b: {
        b1: 2
      }
};
var res3 = Object.assign({}, obj1, obj3); // b2,b3 keys are lost
document.write('res3: ', JSON.stringify (res3), '<br>');
// Output:
  // res3: {"a":1,"b":{"b1":2}}  // NOTE: b2,b3 values are lost

Еще несколько небольших примеров, также для массива и объекта:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

необходим, когда целевой объект является константой, и вы хотите установить несколько свойств одновременно.

Например:

      const target = { data: "Test", loading: true }

Теперь предположим, что вам нужно изменить цель со всеми свойствами из источника:

      const source = { data: null, loading: false, ...etc }

Object.assign(target, source) // Now target is updated
target = { ...target, ...source) // Error: cant assign to constant

Имейте в виду, что вы изменяете целевой объект, поэтому, когда это возможно, используйте Object.assignс пустой целью или разворотом для назначения новому объекту.

Я хотел бы добавить этот простой пример, когда вам нужно использовать Object.assign.

class SomeClass {
  constructor() {
    this.someValue = 'some value';
  }

  someMethod() {
    console.log('some action');
  }
}


const objectAssign = Object.assign(new SomeClass(), {});
objectAssign.someValue; // ok
objectAssign.someMethod(); // ok

const spread = {...new SomeClass()};
spread.someValue; // ok
spread.someMethod(); // there is no methods of SomeClass!

Это может быть непонятно при использовании JavaScript. Но с TypeScript проще, если вы хотите создать экземпляр некоторого класса

const spread: SomeClass = {...new SomeClass()} // Error

Теперь это часть ES6, поэтому оно стандартизировано и задокументировано в MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

Это очень удобно для использования и имеет большой смысл наряду с деструктуризацией объектов.

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

Поэтому правильным ответом будет использование разброса объектов, поскольку теперь он стандартизирован, широко используется (см. "Реакция", "Редукс" и т. Д.), Прост в использовании и обладает всеми функциями Object.assign ().

Оператор распространения разбивает массив на отдельные аргументы функции.

let iterableObjB = [1,2,3,4]
function (...iterableObjB)  //turned into
function (1,2,3,4)

Мы создадим функцию с именем identity, которая просто возвращает любой параметр, который мы ей даем.

identity = (arg) => arg

И простой массив.

arr = [1, 2, 3]

Если вы позвоните в личность с помощью arr, мы знаем, что произойдет

Другие вопросы по тегам