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


396

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

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

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

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

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

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

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


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

5
Определите « лучший » (осторожно, не заканчивайте вопросом, основанным на мнении :-)
Amit

1
Также может зависеть, как вы хотите его поддерживать, если работаете в среде без встроенной поддержки. Синтаксис вы можете просто скомпилировать. Объект или метод, который вам может понадобиться для заполнения.
JMM

6
Помимо проблем совместимости, Object.assign может изменять исходный объект, что полезно. распространяться не может.
pstanton

2
Чтобы уточнить комментарий @ pstanton - object.assign может изменить существующий целевой объект (перезаписать свойства из источника, оставив другие свойства без изменений); это не касается исходного объекта. Сначала я прочитал его «оригинальный объект» как «исходный объект», поэтому пишу эту заметку для всех, кто так же неправильно ее читает. :)
ToolmakerSteve

Ответы:


328

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

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

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

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

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

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

Недостатки:

  • Когда этот ответ был изначально написан, это было предложение , не стандартизированное. При использовании предложений учитывайте, что вы будете делать, если вы сейчас пишете код с ним, и он не стандартизируется и не изменяется по мере продвижения к стандартизации. С тех пор это было стандартизировано в ES2018.

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


Object.assign()

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

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

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

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

    var sources = [{a: "A"}, {b: "B"}, {c: "C"}];
    options = Object.assign.apply(Object, [{}].concat(sources));
    // or
    options = Object.assign({}, ...sources);

Недостатки:

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

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

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


15
Может быть , стоит отметить , что объект остальное распространение перешел на стадию 3 , так, вероятно , будет стандартизирована в будущем twitter.com/sebmarkbage/status/781564713750573056
williaster

11
@JMM Я не уверен, что вижу «Более многословно». как недостаток.
DiverseAndRemote.com

6
@ Хорошо, я думаю, вы только что доказали, что это мнение :) Если кто-то не признает это преимущество, тогда да, при прочих равных они могут просто использовать Object.assign(). Или вы можете вручную перебирать массив объектов и их собственные реквизиты, вручную назначая их цели и
JMM

7
как упомянул @JMM, теперь он находится в спецификации узла ES2018.green/#ES2018-features-object-rest-spread-properties
Себастьен Х.

2
«Каждый байт имеет значение» Вот то, что минимизаторы / углификации для @yzorg
DiverseAndRemote.com

171

Для эталонного объекта отдых / разворот завершается в 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];
const z = null;

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

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

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}

10
Это должен быть выбранный ответ.
Эван Плейс,

4
Это должен быть ответ ... это будущее сейчас.
Захари Абреш

1
Это правильный ответ. Основное отличие заключается в Object.assignиспользовании сеттеров. Object.assign({set a(v){this.b=v}, b:2}, {a:4}); // {b: 4}против{...{set a(v){this.b=v}, b:2}, ...{a:4}}; // {a: 4, b: 2}
Дэвид Бохо

1
"Будущее - сегодня!" - Джордж Аллен Завтра слишком поздно.
Ерф

1
Демонстрация обращения с нулем по-разному - «яблоки и апельсины», а не сравнение. В случае массива null является элементом массива. В распространенном случае ноль - это весь объект. Правильное сравнение было бы для х , чтобы иметь свойство нуля : const x = {c: null};. В этом случае, AFAIK, мы хотели бы видеть поведение так же , как массив: //{a: 1, b: 2, c: null}.
ToolmakerSteve

39

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

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

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

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


«не будет сохранять прототип в целости и сохранности» - это, безусловно, будет, так как он ничего не меняет. В вашем примере, errorExtendedUsingAssign === errorно errorExtendedUsingSpreadэто новый объект (и прототип не был скопирован).
Maaartinus

2
@maaartinus Вы правы, я, наверное, плохо сформулировал. Я имел в виду, что прототип не находится на скопированном объекте. Я мог бы отредактировать это, чтобы быть более ясным.
Шон Доусон

Является ли следующий способ «мелкого клонирования» объекта с его классом? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve

@ToolmakerSteve Да, он скопирует все «собственные свойства» объекта, которые фактически будут мелким клоном. См .: stackoverflow.com/questions/33692912/…
Шон Доусон,

12

Как уже упоминали другие, в этот момент написания 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);

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

Вот вывод из Бабеля в 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
Спасибо! Ваш пример кода делает решение действительно легким для моего контекста. Транспортер (babel или машинописный текст) делает оператор распространения более совместимым с браузерами, добавляя pollyfill во встроенный код. Просто для интереса перенесенная версия TypeScript практически не отличается от Babel: typescriptlang.org/play/…
Марк Уитфельд,

2
хм ... разве ваши два дела не дают одинаковых результатов? в первом случае вы копируете свойства из одного объекта в другой, а в другом вы создаете новый объект. Object.assign возвращает цель, поэтому в первом случае objAss и newObjAss совпадают.
Кевин Б.

Добавление нового первого параметра {}должно исправить несоответствие.
Кевин Б.

11

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

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

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

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

34
Это не синтаксический сахар Object.assign, так как оператор распространения всегда даст вам новый объект.
MaxArt

4
На самом деле, я удивлен, что другие люди больше не подчеркивают разницу в изменчивости. Вспомните все потраченные часы разработчика на отладку случайных мутаций с помощью Object.assign
deepelement

Теперь это поддерживается в большинстве современных браузеров (как и в других ES6): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991

11

Я хотел бы обобщить состояние функции 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. Но спустя годы Ричард оказался прав (на мой взгляд). Поэтому я пишу этот ответ вместо этого, надеясь, что в конечном итоге он получит поддержку старых ответов (это может занять годы, но в конце концов это и есть эффект длинного хвоста ).


3
ваш раздел «почему ответ», вероятно, не нужен
LocustHorde

@LocustHorde Может быть, я мог бы переместить 2-й абзац (почему эту тему так сложно найти в Google) в отдельный раздел. Тогда остальные могут вписаться в комментарий.
Ёзорг

8

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

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

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

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

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


3
Почему написано «Копировать»? Каковы эти смелые заголовки. Я чувствую, что пропустил какой-то контекст, когда прочитал это ...
ADJenks

2

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

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


1

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

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

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

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


2
Нет, это не часть ES6. Предоставленная вами ссылка относится только к использованию оператора распространения на массивах . Использование оператора распространения на объектах в настоящее время является предложением стадии 2 (т.е. черновой), как объяснено в ответе JMM.
ChillyPenguin

1
Я не думаю, что когда-либо видел ответ, полностью основанный на ложной информации. Даже через год это не является частью спецификации ES и не поддерживается в большинстве сред.
3

Чилли и 3о оказались неправы, Ричард прав. Поддержка браузера и инструментов - все это происходит, но прошло уже 1,5 года после ответа Ричарда. См. Мой новый ответ для сводной информации о поддержке по состоянию на март 2018 г.
yzorg

0

Я хотел бы добавить этот простой пример, когда вам нужно использовать 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.