Diffusione oggetto vs. Object.assign


396

Diciamo che ho una optionsvariabile e voglio impostare un valore predefinito.

Qual è il vantaggio / svantaggio di queste due alternative?

Utilizzo della diffusione degli oggetti

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

O usando Object.assign

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

Questo è l' impegno che mi ha fatto meravigliare.


4
Bene, la prima è una nuova sintassi proposta e non fa parte di ES6, quindi dipende dallo standard a cui vuoi aderire.
loganfsmyth,

5
Definisci " meglio " (attenzione, non finire con una domanda basata sull'opinione :-)
Tra il

1
Potrebbe anche dipendere da come si desidera supportarlo se eseguito in ambienti senza supporto nativo. Sintassi che potresti riuscire a compilare. Un oggetto o un metodo che potrebbe essere necessario eseguire il polyfill.
JMM,

6
a parte i problemi di compatibilità, Object.assign può mutare l'oggetto originale che è utile. la diffusione non può.
Pstanton,

2
Per chiarire il commento di @ pstanton - object.assign può modificare un oggetto target esistente (sovrascrivere le proprietà dall'origine, lasciando intatte le altre proprietà); non tocca l' oggetto sorgente . Ho prima letto il suo "oggetto originale" come "oggetto sorgente", quindi scrivendo questa nota per chiunque lo abbia interpretato in modo simile. :)
ToolmakerSteve

Risposte:


328

Questo non è necessariamente esaustivo.

Diffondere la sintassi

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

vantaggi:

  • Se si crea codice per l'esecuzione in ambienti senza supporto nativo, è possibile compilare questa sintassi (anziché utilizzare un polyfill). (Con Babele, per esempio.)

  • Meno prolisso.

svantaggi:

  • Quando questa risposta era stata originariamente scritta, questa era una proposta , non standardizzata. Quando usi le proposte considera cosa faresti se scrivessi codice con esso ora e non viene standardizzato o cambia mentre si sposta verso la standardizzazione. Da allora questo è stato standardizzato in ES2018.

  • Letterale, non dinamico.


Object.assign()

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

vantaggi:

  • Standardizzato.

  • Dinamico. Esempio:

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

svantaggi:

  • Più prolisso.
  • Se si crea codice per l'esecuzione in ambienti senza supporto nativo, è necessario eseguire il polyfill.

Questo è l'impegno che mi ha fatto meravigliare.

Non è direttamente correlato a ciò che stai chiedendo. Quel codice non stava usando Object.assign(), stava usando il codice utente ( object-assign) che fa la stessa cosa. Sembrano compilare quel codice con Babel (e raggrupparlo con Webpack), che è quello di cui stavo parlando: la sintassi che puoi semplicemente compilare. Apparentemente preferivano quello di dover includere object-assigncome dipendenza che sarebbe andata a buon fine.


15
Vale la pena notare che la diffusione del resto dell'oggetto è passata alla fase 3, quindi sarà probabilmente standardizzata in futuro twitter.com/sebmarkbage/status/781564713750573056
williaster,

11
@JMM Non sono sicuro di vedere "Più dettagliato". come svantaggio.
DiverseAndRemote.com il

6
@Omar beh, immagino tu abbia appena dimostrato che è un'opinione :) Se qualcuno non riconosce quel vantaggio, sì, tutto il resto è uguale che possono usare Object.assign(). Oppure potresti iterare manualmente su una matrice di oggetti e i loro oggetti di scena assegnandoli manualmente a un obiettivo e renderlo ancora più dettagliato: P
JMM

7
come menzionato da @JMM, ora è nelle specifiche ES2018 node.green/#ES2018-features-object-rest-spread-properties
Sebastien H.

2
"Ogni byte conta" Ecco cos'è minimizer / uglify per @yzorg
DiverseAndRemote.com

171

Per riferimento oggetto resto / diffusione è finalizzato in ECMAScript 2018 come fase 4. La proposta è disponibile qui .

Per la maggior parte il reset e la diffusione degli oggetti funzionano allo stesso modo, la differenza chiave è che spread definisce le proprietà, mentre Object.assign () le imposta . Ciò significa che Object.assign () attiva i setter.

Vale la pena ricordare che, a parte questo, l'oggetto rest / spread 1: 1 esegue la mappatura su Object.assign () e agisce in modo diverso sulla diffusione dell'array (iterabile). Ad esempio, durante la diffusione di un array vengono diffusi valori null. Tuttavia, usando i valori null di diffusione dell'oggetto vengono silenziosamente diffusi a nulla.

Array (Iterable) Esempio di diffusione

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

Esempio di diffusione dell'oggetto

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

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

Ciò è coerente con come funzionerebbe Object.assign (), entrambi escludere silenziosamente il valore null senza errori.

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

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

10
Questa dovrebbe essere la risposta selezionata.
Evan Plaice,

4
Questa dovrebbe essere la risposta ... è il futuro adesso.
Zachary Abresch,

1
Questa è la risposta esatta. Il principale è Object.assignutilizzare i setter. 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}
David Boho,

1
"Il futuro è ora!" - George Allen Domani è troppo tardi.
ruffin,

1
La dimostrazione che nulla viene gestito diversamente è "mele e arance", non un confronto significativo. Nel caso dell'array, null è un elemento dell'array. Nel caso spread, null è l'intero oggetto. Il confronto corretto sarebbe per x di avere un immobile nulla : const x = {c: null};. In questo caso, per quanto ne so, si vedrebbe un comportamento proprio come la matrice: //{a: 1, b: 2, c: null}.
ToolmakerSteve

39

Penso che una grande differenza tra l'operatore di diffusione e Object.assignche non sembra essere menzionato nelle risposte attuali è che l'operatore di diffusione non copierà il prototipo dell'oggetto di origine sull'oggetto di destinazione. Se si desidera aggiungere proprietà a un oggetto e non si desidera modificare l'istanza di cui si tratta, è necessario utilizzare Object.assign. L'esempio seguente dovrebbe dimostrare questo:

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


"non manterrà intatto il prototipo" - sicuramente lo farà, in quanto non modifica nulla. Nel tuo esempio, errorExtendedUsingAssign === errorma errorExtendedUsingSpreadè un nuovo oggetto (e il prototipo non è stato copiato).
maaartinus,

2
@maaartinus Hai ragione, probabilmente l'ho scritto male. Intendevo dire che il prototipo non si trova sull'oggetto copiato. Potrei modificarlo per essere più chiaro.
Sean Dawson,

Il seguente è un modo per "clonare superficialmente" un oggetto con la sua classe? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve

@ToolmakerSteve Sì, copierà tutte le "proprietà proprie" dell'oggetto attraverso le quali sarà effettivamente un clone superficiale. Vedi: stackoverflow.com/questions/33692912/…
Sean Dawson,

12

Come altri hanno già detto, in questo momento in cui si scrive, è Object.assign()necessario un polyfill e la diffusione dell'oggetto ...richiede un po 'di transpiling (e forse anche un polyfill) per funzionare.

Considera questo codice:

// 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);

Entrambi producono lo stesso output.

Ecco l'output di Babel, a 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);

Questa è la mia comprensione finora. Object.assign()è in realtà standardizzato, dove la diffusione dell'oggetto ...non è ancora. L'unico problema è il supporto del browser per il primo e in futuro, anche per il secondo.

Gioca con il codice qui

Spero che sia di aiuto.


1
Grazie! Il tuo esempio di codice rende la decisione davvero semplice per il mio contesto. Il transpiler (babel o dattiloscritto) rende l'operatore di diffusione più compatibile con i browser includendo un pollyfill nel codice inline. Solo per interesse, la versione tradotta di TypeScript è praticamente la stessa di Babele: typescriptlang.org/play/…
Mark Whitfeld,

2
hmm ... i tuoi due casi non avrebbero prodotto gli stessi risultati? nel primo caso stai copiando le proprietà da un oggetto a un altro, e nell'altro stai creando un nuovo oggetto. Object.assign restituisce la destinazione, quindi nel tuo primo caso objAss e newObjAss sono uguali.
Kevin B,

L'aggiunta di un nuovo primo parametro di {}dovrebbe correggere l'incoerenza.
Kevin B,

11

L'operatore diffusione oggetto (...) non funziona nei browser, perché non fa ancora parte di alcuna specifica ES, ma solo una proposta. L'unica opzione è compilarlo con Babel (o qualcosa di simile).

Come puoi vedere, è solo zucchero sintattico su Object.assign ({}).

Per quanto posso vedere, queste sono le differenze importanti.

  • Object.assign funziona nella maggior parte dei browser (senza compilazione)
  • ... per gli oggetti non è standardizzato
  • ... ti protegge dalla mutazione accidentale dell'oggetto
  • ... eseguirà il polyfill di Object.assign nei browser senza di esso
  • ... ha bisogno di meno codice per esprimere la stessa idea

34
E ' non è zucchero sintattico per Object.assign, come l'operatore diffusione vi darà sempre un nuovo oggetto.
MaxArt,

4
In realtà, sono sorpreso che le altre persone non sottolineino più la differenza di mutabilità. Pensa a tutte le ore di sviluppo perse durante il debug delle mutazioni accidentali con Object.assign
deepelement

Questo è ora supportato nella maggior parte dei browser moderni (come con altri ES6): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991

11

Vorrei riassumere lo stato della funzione ES "Unisci oggetti uniti", nei browser e nell'ecosistema tramite strumenti.

Spec

Browser: presto in Chrome, in SF, Firefox (ver 60, IIUC)

  • Supporto del browser per "proprietà di diffusione" fornito in Chrome 60 , incluso questo scenario.
  • Il supporto per questo scenario NON funziona nell'attuale Firefox (59), ma funziona nella mia Firefox Developer Edition. Quindi credo che verrà spedito in Firefox 60.
  • Safari: non testato, ma Kangax dice che funziona in Desktop Safari 11.1, ma non in SF 11
  • Safari iOS: non segnato, ma Kangax dice che funziona su iOS 11.3, ma non su iOS 11
  • non ancora in Edge

Strumenti: Nodo 8.7, TS 2.1

link

Esempio di codice (raddoppia come test di compatibilità)

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 }

Di nuovo: al momento della stesura di questo esempio funziona senza transpilation in Chrome (60+), Firefox Developer Edition (anteprima di Firefox 60) e Node (8.7+).

Perché rispondere?

Sto scrivendo questo 2,5 anni dopo la domanda originale. Ma avevo la stessa domanda, ed è qui che mi ha inviato Google. Sono schiavo della missione di SO di migliorare la coda lunga.

Dato che si tratta di un'espansione della sintassi "array spread", ho trovato molto difficile google e difficile da trovare nelle tabelle di compatibilità. Il più vicino che ho trovato è la "proprietà spread" di Kangax , ma quel test non ha due spread nella stessa espressione (non una fusione). Inoltre, il nome nelle pagine di stato proposizioni / bozze / browser utilizza tutti "proprietà sparsa", ma mi sembra che sia stato un "primo preside" a cui la comunità è arrivata dopo le proposte per usare la sintassi della diffusione per "unione di oggetti". (Il che potrebbe spiegare perché è così difficile google.) Quindi documento la mia scoperta qui in modo che altri possano visualizzare, aggiornare e compilare collegamenti su questa specifica funzionalità. Spero che prenda piede. Per favore, aiutaci a diffondere le notizie che arrivano nelle specifiche e nei browser.

Infine, avrei aggiunto queste informazioni come commento, ma non sono riuscito a modificarle senza rompere l'intento originale degli autori. In particolare, non posso modificare il commento di @ ChillyPenguin senza perdere l'intenzione di correggere @RichardSchulte. Ma anni dopo Richard si rivelò giusto (secondo me). Quindi scrivo invece questa risposta, sperando che alla fine possa guadagnare trazione sulle vecchie risposte (potrebbe richiedere anni, ma questo è l' effetto di coda lunga , dopo tutto).


3
la sezione "perché rispondi" probabilmente non è necessaria
LocustHorde,

@LocustHorde Forse potrei spostare il secondo paragrafo (perché questo argomento è così difficile da google) nella sua sezione. Quindi il resto potrebbe inserirsi in un commento.
yzorg,

8

NOTA: Diffondere NON è solo zucchero sintattico attorno a Object.assign. Operano in modo molto diverso dietro le quinte.

Object.assign applica i setter a un nuovo oggetto, Spread no. Inoltre, l'oggetto deve essere iterabile.

Copia Utilizzare questo se è necessario il valore dell'oggetto così com'è in questo momento e non si desidera che quel valore rifletta le modifiche apportate da altri proprietari dell'oggetto.

Usalo per creare una copia superficiale dell'oggetto buona pratica per impostare sempre le proprietà immutabili da copiare - poiché le versioni mutabili possono essere passate in proprietà immutabili, la copia assicurerà che avrai sempre a che fare con un oggetto immutabile

Assegna Assegna è un po 'l'opposto della copia. Assign genererà un setter che assegna direttamente il valore alla variabile di istanza, anziché copiarlo o conservarlo. Quando si chiama il getter di una proprietà di assegnazione, restituisce un riferimento ai dati effettivi.


3
Perché dice "Copia"? Quali sono questi titoli audaci. Mi sembra di aver perso un po 'di contesto quando ho letto questo ...
ADJenks

2

Altre risposte sono vecchie, impossibile ottenere una buona risposta.
L'esempio seguente è per i letterali di oggetti, aiuta come entrambi possono completarsi a vicenda e come non può completarsi a vicenda (quindi differenza):

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

Molti altri piccoli esempi qui, anche per array e oggetti:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax


1

Questo fa ora parte di ES6, quindi è standardizzato ed è anche documentato su MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

È molto comodo da usare e ha molto senso accanto alla destrutturazione degli oggetti.

L'unico vantaggio rimanente sopra elencato sono le capacità dinamiche di Object.assign (), tuttavia è facile come diffondere l'array all'interno di un oggetto letterale. Nell'output compilato di babel usa esattamente ciò che è dimostrato con Object.assign ()

Quindi la risposta corretta sarebbe quella di usare la diffusione dell'oggetto poiché ora è standardizzata, ampiamente usata (vedi reagire, redux, ecc.), È facile da usare e ha tutte le caratteristiche di Object.assign ()


2
No, non fa parte di ES6. Il collegamento fornito si riferisce all'uso dell'operatore di diffusione solo sugli array . L'uso dell'operatore spread sugli oggetti è attualmente una proposta Stage 2 (ovvero Bozza), come spiegato nella risposta di JMM.
ChillyPenguin,

1
Non credo di aver mai visto una risposta così interamente basata su informazioni false. Anche un anno dopo, ciò non fa parte delle specifiche ES e non è supportato nella maggior parte degli ambienti.
3ocene,

Chilly e 3o si sono rivelati sbagliati, Richard ha ragione. Il supporto del browser e il supporto degli strumenti stanno tutti atterrando, ma ci sono voluti 1,5 anni dopo la risposta di Richard. Vedere la mia nuova risposta per un riepilogo del supporto a partire da marzo 2018.
yzorg

0

Vorrei aggiungere questo semplice esempio quando devi usare 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!

Può non essere chiaro quando si utilizza JavaScript. Ma con TypeScript è più semplice se si desidera creare un'istanza di una classe

const spread: SomeClass = {...new SomeClass()} // Error
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.