Répartition des objets vs Object.assign

396

Disons que j'ai une optionsvariable et que je veux définir une valeur par défaut.

Quels sont les avantages / inconvénients de ces deux alternatives?

Utilisation de la propagation d'objets

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

Ou en utilisant Object.assign

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

C'est l' engagement qui m'a fait me demander.

Olivier Tassinari
la source
4
Eh bien, la première est une nouvelle syntaxe proposée et ne fait pas partie d'ES6, cela dépend donc de la norme à laquelle vous souhaitez adhérer.
loganfsmyth
5
Définissez «le meilleur » (soigneusement, ne vous retrouvez pas avec une question d'opinion :-)
Amit
1
Pourrait également dépendre de la façon dont vous souhaitez le prendre en charge s'il s'exécute dans des environnements sans prise en charge native. Syntaxe que vous pourriez peut-être simplement compiler. Un objet ou une méthode que vous pourriez avoir besoin de polyfill.
JMM
6
en dehors des problèmes de compatibilité, Object.assign peut muter l'objet d'origine qui est utile. la propagation ne peut pas.
pstanton
2
Pour clarifier le commentaire de @ pstanton - object.assign peut modifier un objet cible existant (écraser les propriétés de la source, tout en laissant les autres propriétés intactes); il ne touche pas l' objet source . J'ai d'abord lu son "objet d'origine" comme "objet source", donc j'écris cette note pour toute autre personne qui l'aurait mal lu. :)
ToolmakerSteve

Réponses:

328

Ce n'est pas nécessairement exhaustif.

Syntaxe de propagation

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

Avantages:

  • Si vous créez du code pour une exécution dans des environnements sans prise en charge native, vous pourrez peut-être simplement compiler cette syntaxe (par opposition à l'utilisation d'un polyfill). (Avec Babel, par exemple.)

  • Moins verbeux.

Désavantages:

  • Lorsque cette réponse a été écrite à l'origine, il s'agissait d'une proposition , non standardisée. Lorsque vous utilisez des propositions, tenez compte de ce que vous feriez si vous écrivez du code avec celui-ci maintenant et qu'il n'est pas standardisé ou ne change pas à mesure qu'il se dirige vers la standardisation. Cela a depuis été normalisé dans ES2018.

  • Littéral, pas dynamique.


Object.assign()

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

Avantages:

  • Standardisé.

  • Dynamique. Exemple:

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

Désavantages:

  • Plus verbeux.
  • Si vous créez du code pour une exécution dans des environnements sans prise en charge native, vous devez effectuer un polyfill.

C'est l'engagement qui m'a fait me demander.

Ce n'est pas directement lié à ce que vous demandez. Ce code n'utilisait pas Object.assign(), il utilisait le code utilisateur ( object-assign) qui fait la même chose. Ils semblent compiler ce code avec Babel (et le regrouper avec Webpack), ce dont je parlais: la syntaxe que vous pouvez simplement compiler. Ils ont apparemment préféré cela à l'obligation d'inclure object-assigncomme dépendance qui irait dans leur build.

JMM
la source
15
Il convient de noter que la propagation du repos d'objet est passée à l'étape 3 et sera probablement normalisée à l'avenir twitter.com/sebmarkbage/status/781564713750573056
williaster
11
@JMM Je ne suis pas sûr de voir "Plus verbeux". comme un inconvénient.
DiverseAndRemote.com
6
@Omar, je suppose que vous venez de prouver que c'est une opinion :) Si quelqu'un ne reconnaît pas cet avantage, alors, toutes choses étant égales par ailleurs, il peut simplement l'utiliser Object.assign(). Ou vous pouvez parcourir manuellement un tableau d'objets et leurs propres accessoires en les affectant manuellement à une cible et le rendre encore plus détaillé: P
JMM
7
comme @JMM l'a mentionné, il est maintenant dans la spécification ES2018 node.green/#ES2018-features-object-rest-spread-properties
Sebastien H.
2
"Chaque octet compte" C'est ce que les minimiseurs / uglify sont pour @yzorg
DiverseAndRemote.com
171

Pour le reste / la répartition de l'objet de référence est finalisé dans ECMAScript 2018 comme étape 4. La proposition peut être trouvée ici .

Pour la plupart, la réinitialisation et la répartition des objets fonctionnent de la même manière, la principale différence est que la répartition définit les propriétés, tandis que Object.assign () les définit . Cela signifie que Object.assign () déclenche des setters.

Il convient de se rappeler qu'à part cela, le repos / propagation d'objet 1: 1 correspond à Object.assign () et agit différemment de la propagation de tableau (itérable). Par exemple, lors de la propagation d'un tableau, les valeurs nulles sont réparties. Cependant, l'utilisation de valeurs nulles réparties sur un objet est silencieusement répartie sur rien.

Exemple de propagation en tableau (itérable)

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

Exemple de propagation d'objet

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

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

Ceci est cohérent avec la façon dont Object.assign () fonctionnerait, les deux excluent silencieusement la valeur nulle sans erreur.

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

console.log(z); //{a: 1, b: 2}
tomhughes
la source
10
Cela devrait être la réponse choisie.
Evan Plaice
4
Cela devrait être la réponse ... c'est l'avenir maintenant.
Zachary Abresch
1
C'est la bonne réponse. Le principal est d' Object.assignutiliser des setters. 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
"Le futur c'est maintenant!" - George Allen demain est trop tard.
ruffin
1
La démonstration que null est traité différemment est "des pommes et des oranges" - pas une comparaison significative. Dans le cas d'un tableau, null est un élément du tableau. Dans le cas de propagation, null est l'objet entier. La comparaison correcte serait pour x d'avoir une propriété nulle : const x = {c: null};. Dans ce cas, autant que je sache, nous verrions un comportement tout comme le tableau: //{a: 1, b: 2, c: null}.
ToolmakerSteve
39

Je pense qu'une grande différence entre l'opérateur de diffusion et Object.assignqui ne semble pas être mentionnée dans les réponses actuelles est que l'opérateur de diffusion ne copiera pas le prototype de l'objet source vers l'objet cible. Si vous voulez ajouter des propriétés à un objet et que vous ne voulez pas changer de quelle instance il s'agit, alors vous devrez utiliser Object.assign. L'exemple ci-dessous devrait le démontrer:

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

Sean Dawson
la source
"ne gardera pas le prototype intact" - il le fera sûrement, car il ne modifie rien. Dans votre exemple, errorExtendedUsingAssign === errormais errorExtendedUsingSpreadc'est un nouvel objet (et le prototype n'a pas été copié).
maaartinus
2
@maaartinus Vous avez raison, j'ai probablement mal formulé cela. Je voulais dire que le prototype n'est pas sur l'objet copié. Je pourrais éditer cela pour être plus clair.
Sean Dawson
Est-ce que ce qui suit est un moyen de "cloner peu profond" un objet avec sa classe? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve
@ToolmakerSteve Oui, il copiera toutes les "propres propriétés" de l'objet à travers lesquelles sera effectivement un clone peu profond. Voir: stackoverflow.com/questions/33692912/…
Sean Dawson
12

Comme d'autres l'ont mentionné, à ce moment de la rédaction, il Object.assign()faut un polyfill et la propagation d'objet ...nécessite un transpilage (et peut-être aussi un polyfill) pour fonctionner.

Considérez ce code:

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

Ces deux produisent la même sortie.

Voici la sortie de Babel, vers 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);

Telle est ma compréhension jusqu'à présent. Object.assign()est en fait standardisé, alors que la propagation des objets ...ne l'est pas encore. Le seul problème est la prise en charge du navigateur pour les premiers et à l'avenir, les seconds aussi.

Jouez avec le code ici

J'espère que cela t'aides.

Michael Giovanni Pumo
la source
1
Je vous remercie! Votre exemple de code rend la décision très simple pour mon contexte. Le transpilateur (babel ou dactylographié) rend l'opérateur de propagation plus compatible avec les navigateurs en incluant un pollyfill dans le code en ligne. Juste pour votre intérêt, la version transposée de TypeScript est pratiquement la même que Babel: typescriptlang.org/play/…
Mark Whitfeld
2
hmm ... vos deux cas ne produiraient-ils pas les mêmes résultats? dans le premier cas, vous copiez des propriétés d'un objet à un autre et dans l'autre, vous créez un nouvel objet. Object.assign renvoie la cible, donc dans votre premier cas objAss et newObjAss sont les mêmes.
Kevin B
L'ajout d'un nouveau premier paramètre de {}devrait corriger l'incohérence.
Kevin B
11

L'opérateur de propagation d'objet (...) ne fonctionne pas dans les navigateurs, car il ne fait pas encore partie d'une spécification ES, juste une proposition. La seule option est de le compiler avec Babel (ou quelque chose de similaire).

Comme vous pouvez le voir, c'est juste du sucre syntaxique sur Object.assign ({}).

Pour autant que je puisse voir, ce sont les différences importantes.

  • Object.assign fonctionne dans la plupart des navigateurs (sans compilation)
  • ... pour les objets n'est pas standardisé
  • ... vous protège contre la mutation accidentelle de l'objet
  • ... polyfill Object.assign dans les navigateurs sans elle
  • ... a besoin de moins de code pour exprimer la même idée
Karthick Kumar
la source
34
Ce n'est pas du sucre syntaxique Object.assign, car l'opérateur de propagation vous donnera toujours un nouvel objet.
MaxArt
4
En fait, je suis surpris que d'autres personnes ne mettent pas davantage l'accent sur la différence de mutabilité. Pensez à toutes les heures de développement perdues lors du débogage de mutations accidentelles avec Object.assign
deepelement
Ceci est désormais pris en charge dans la plupart des navigateurs modernes (comme avec les autres ES6): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991
11

Je voudrais résumer l'état de la fonctionnalité ES "fusion d'objets dispersés", dans les navigateurs et dans l'écosystème via des outils.

Spec

Navigateurs: dans Chrome, dans SF, Firefox bientôt (ver 60, IIUC)

  • Prise en charge par le navigateur des "propriétés de diffusion" livrées dans Chrome 60 , y compris ce scénario.
  • La prise en charge de ce scénario ne fonctionne PAS dans Firefox actuel (59), mais fonctionne dans mon Firefox Developer Edition. Je pense donc qu'il sera livré dans Firefox 60.
  • Safari: non testé, mais Kangax dit que cela fonctionne dans Desktop Safari 11.1, mais pas SF 11
  • iOS Safari: non testé, mais Kangax dit que cela fonctionne dans iOS 11.3, mais pas dans iOS 11
  • pas encore dans Edge

Outils: Node 8.7, TS 2.1

  • NodeJS est pris en charge depuis la version 8.7 (via Kangax). Confirmé le 9.8 lorsque j'ai testé localement.
  • TypeScript le supporte depuis 2.1 , le courant est 2.8

Liens

Exemple de code (sert également de test de 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 }

Encore une fois: au moment de la rédaction de cet exemple, il fonctionne sans transpilation dans Chrome (60+), Firefox Developer Edition (préversion de Firefox 60) et Node (8.7+).

Pourquoi répondre?

J'écris ceci 2,5 ans après la question d'origine. Mais j'avais la même question, et c'est là que Google m'a envoyé. Je suis esclave de la mission de SO d'améliorer la longue queue.

Puisqu'il s'agit d'une extension de la syntaxe de «propagation de tableau», j'ai trouvé qu'il était très difficile de google, et difficile à trouver dans les tableaux de compatibilité. Le plus proche que j'ai pu trouver est Kangax "propagation de propriété" , mais ce test n'a pas deux spreads dans la même expression (pas une fusion). De plus, le nom dans les pages de propositions / brouillons / état du navigateur utilise tous "propriété répartie", mais il me semble que c'était un "premier principe" auquel la communauté est arrivée après les propositions d'utiliser la syntaxe répartie pour "fusion d'objets". (Ce qui pourrait expliquer pourquoi il est si difficile de google.) Je documente donc mes résultats ici afin que d'autres puissent afficher, mettre à jour et compiler des liens sur cette fonctionnalité spécifique. J'espère que ça va continuer. Aidez-nous à diffuser la nouvelle de son atterrissage dans la spécification et dans les navigateurs.

Enfin, j'aurais ajouté cette information en tant que commentaire, mais je n'ai pas pu les modifier sans casser l'intention originale des auteurs. Plus précisément, je ne peux pas modifier le commentaire de @ ChillyPenguin sans perdre son intention de corriger @RichardSchulte. Mais des années plus tard, Richard s'est avéré avoir raison (à mon avis). J'écris donc cette réponse à la place, en espérant qu'elle finira par gagner du terrain sur les anciennes réponses (cela pourrait prendre des années, mais c'est de cela qu'il s'agit après tout).

yzorg
la source
3
votre section "pourquoi répondre" n'est probablement pas nécessaire
LocustHorde
@LocustHorde Je pourrais peut-être déplacer le 2ème paragraphe (pourquoi ce sujet est si difficile à google) dans sa propre section. Ensuite, le reste pourrait entrer dans un commentaire.
yzorg
8

REMARQUE: la propagation n'est PAS seulement du sucre syntaxique autour d'Object.assign. Ils fonctionnent très différemment en coulisses.

Object.assign applique des setters à un nouvel objet, Spread ne le fait pas. De plus, l'objet doit être itérable.

Copier Utilisez ceci si vous avez besoin de la valeur de l'objet telle qu'elle est en ce moment et que vous ne voulez pas que cette valeur reflète les modifications apportées par d'autres propriétaires de l'objet.

Utilisez-le pour créer une copie superficielle de l'objet bonne pratique pour toujours définir des propriétés immuables sur copier - parce que les versions mutables peuvent être passées en propriétés immuables, la copie garantira que vous aurez toujours affaire à un objet immuable

Assigner Assigner est quelque peu l'opposé de copier. Assign génère un setter qui affecte directement la valeur à la variable d'instance, plutôt que de la copier ou de la conserver. Lors de l'appel du getter d'une propriété assign, il renvoie une référence aux données réelles.

Charles Owen
la source
3
Pourquoi dit-il "Copier"? Quelles sont ces rubriques audacieuses. J'ai l'impression d'avoir manqué un peu de contexte en lisant ceci ...
ADJenks
2

Les autres réponses sont anciennes, impossible d'obtenir une bonne réponse.
L'exemple ci-dessous concerne les littéraux d'objets, explique comment les deux peuvent se compléter et comment ils ne peuvent pas se compléter (donc la différence):

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

Plusieurs autres petits exemples ici, également pour tableau et objet:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Manohar Reddy Poreddy
la source
1

Cela fait maintenant partie d'ES6, est donc normalisé et est également documenté sur MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

Il est très pratique à utiliser et a beaucoup de sens à côté de la déstructuration d'objets.

Le dernier avantage répertorié ci-dessus est les capacités dynamiques d'Object.assign (), mais cela est aussi simple que de répartir le tableau à l'intérieur d'un objet littéral. Dans la sortie babel compilée, il utilise exactement ce qui est démontré avec Object.assign ()

Donc, la bonne réponse serait d'utiliser la diffusion d'objets car elle est désormais standardisée, largement utilisée (voir react, redux, etc.), est facile à utiliser et possède toutes les fonctionnalités d'Object.assign ()

Rikki Schulte
la source
2
Non, cela ne fait pas partie d'ES6. Le lien que vous avez fourni fait référence à l'utilisation de l'opérateur d'étalement uniquement sur les tableaux . L'utilisation de l'opérateur d'étalement sur les objets est actuellement une proposition de l'étape 2 (c'est-à-dire une ébauche), comme expliqué dans la réponse de JMM.
ChillyPenguin
1
Je ne pense pas avoir jamais vu une réponse aussi entièrement basée sur de fausses informations. Même un an plus tard, cela ne fait pas partie de la spécification ES et n'est pas pris en charge dans la plupart des environnements.
3ocene
Chilly et 3o se sont avérés faux, Richard a raison. Le support du navigateur et le support des outils arrivent tous, mais cela a pris 1,5 an après la réponse de Richard. Voir ma nouvelle réponse pour un résumé du support en date de mars 2018.
yzorg
0

Je voudrais ajouter cet exemple simple lorsque vous devez utiliser 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!

Cela peut ne pas être clair lorsque vous utilisez JavaScript. Mais avec TypeScript, il est plus facile de créer une instance d'une classe

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