Поширення об’єкта порівняно з 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
@Omar добре, я думаю, ви щойно довели, що це думка :) Якщо хтось не визнає цієї переваги, то так, а всі інші рівні, вони можуть просто використовувати Object.assign(). Або ви можете вручну перебирати масив об'єктів та власні реквізити вручну, призначаючи їх цілі та ще більше розгортаючи: P
JMM

7
як згадував @JMM, тепер він знаходиться в специфікації ES2018 node.green/#ES2018-features-object-rest-spread-properties
Sebastien H.

2
"Кожен байт рахує" Ось що мінімізатори / uglify є для @yzorg
DiverseAndRemote.com

171

Для решти / розповсюдження опорних об'єктів завершено у ECMAScript 2018 як етап 4. Пропозицію можна знайти тут .

Здебільшого скидання та розповсюдження об'єкта працюють однаково, ключовою відмінністю є те, що спред визначає властивості, а Object.assign () встановлює їх . Це означає, що Object.assign () запускає сетери.

Варто пам’ятати, що, крім цього, об'єкт rest / spread 1: 1 відображається в Object.assign () і діє по-іншому для масиву (iterable) розповсюдження. Наприклад, при поширенні масиву нульові значення поширюються. Однак за допомогою об’єктного розвороту нульові значення мовчки поширюються ні до чого.

Приклад поширення масиву (Ітерабельний)

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}vs.{...{set a(v){this.b=v}, b:2}, ...{a:4}}; // {a: 4, b: 2}
Девід Бохо

1
"Майбутнє ЗАРАЗ!" - Джордж Аллен Завтра вже пізно.
ruffin

1
Демонстрація нуля по-різному обробляється "яблуками та апельсинами" - не змістовне порівняння. У випадку масиву null є елементом масиву. У випадку поширення 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);

Обидва вони дають однаковий вихід.

Ось висновок від 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
Дякую! Ваш зразок коду робить рішення дуже простим для мого контексту. Транспілер (babel або typecript) робить оператор розповсюдження більш сумісним для веб-переглядачів, включивши в вбудований код pollyfill. Просто для інтересу машинопису transpiled версія практично такий же , як Вавилонська: typescriptlang.org/play / ...
Марк Whitfeld

2
хм ... хіба два ваші справи не дадуть однакових результатів? у першому випадку ви копіюєте властивості з одного об'єкта на інший, а в другому ви створюєте новий об’єкт. Object.assign повертає ціль, тож у першому випадку objAss та newObjAss однакові.
Кевін Б

Додавання нового першого параметра {}має виправити невідповідність.
Кевін Б

11

Оператор розповсюдження об'єктів (...) не працює у браузерах, оскільки він ще не є частиною жодної специфікації ES, а лише пропозицією. Єдиний варіант - це компілювати його з Бабелем (або чимось подібним).

Як бачите, це просто синтаксичний цукор над 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: не розшитий, але Kangax каже, що він працює в iOS 11.3, але не в iOS 11
  • ще не в Edge

Інструменти: Вузол 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 покращити довгий хвіст.

Оскільки це розширення синтаксису "поширення масиву", мені було дуже важко гугл, і його важко знайти в таблицях сумісності. Найближче, що я міг знайти, - це "поширення власності" в Kangax , але цей тест не має двох розворотів в одному виразі (не об'єднання). Також назва на сторінках пропозицій / чернетів / статусу веб-переглядача використовує "розповсюдження властивостей", але мені здається, що це було "першим головним", до якого спільнота з'явилася після пропозицій використовувати синтаксис спред для "об'єднання об'єктів". (Що може пояснити, чому так важко google.) Тому я документую тут свої висновки, щоб інші могли переглядати, оновлювати та компілювати посилання про цю конкретну особливість. Я сподіваюся, що це наздожене. Будь ласка, допоможіть поширити новини про його посадку в специфікації та в браузерах.

Нарешті, я б додав цю інформацію як коментар, але я не зміг їх відредагувати, не порушивши початкового наміру авторів. Зокрема, я не можу редагувати коментар @ ChillyPenguin, не втративши наміру виправити @RichardSchulte. Але через роки Річард виявився правильним (на мою думку). Тому я пишу цю відповідь замість цього, сподіваючись, що вона згодом здобуде тягу до старих відповідей (це може зайняти роки, але ось, про що йдеться в довгому ефекті хвоста ).


3
розділ "чому відповісти", ймовірно, не потрібен
LocustHorde,

@LocustHorde Можливо, я міг би перенести другий абзац (чому ця тема настільки складна для google) до її власного розділу. Тоді решта може вмістити коментар.
yzorg

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 (), однак це так само просто, як розповсюдження масиву всередині буквального об’єкта. У компільованому виведенні бабелів він використовує саме те, що демонструється 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.