Objeto extendido contra Object.assign

396

Digamos que tengo una optionsvariable y quiero establecer algún valor predeterminado.

¿Cuál es el beneficio / inconveniente de estas dos alternativas?

Usando propagación de objetos

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

O usando Object.assign

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

Este es el compromiso que me hizo preguntarme.

Olivier Tassinari
fuente
44
Bueno, el primero es una nueva sintaxis propuesta y no forma parte de ES6, por lo que depende del estándar al que desee adherirse.
loganfsmyth
55
Defina " mejor " (con cuidado, no termine con una pregunta basada en una opinión :-)
Amit
1
También podría depender de cómo desea admitirlo si se ejecuta en entornos sin soporte nativo. Sintaxis que puede compilar. Un objeto o método que podría necesitar para rellenar.
JMM
66
Además de los problemas de compatibilidad, Object.assign puede mutar el objeto original que es útil. propagación no puede.
pstanton
2
Para aclarar el comentario de @ pstanton, object.assign puede modificar un objeto de destino existente (sobrescribir las propiedades de la fuente, dejando otras propiedades intactas); no toca el objeto fuente . Primero leí su "objeto original" como "objeto fuente", por lo que escribí esta nota para cualquier otra persona que lo lea de manera similar. :)
ToolmakerSteve

Respuestas:

328

Esto no es necesariamente exhaustivo.

Sintaxis extendida

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

Ventajas:

  • Si crea código para su ejecución en entornos sin soporte nativo, es posible que pueda compilar esta sintaxis (en lugar de usar un polyfill). (Con Babel, por ejemplo).

  • Menos detallado.

Desventajas

  • Cuando esta respuesta se escribió originalmente, se trataba de una propuesta , no estandarizada. Cuando use propuestas, considere lo que haría si escribe código con él ahora y no se estandariza o cambia a medida que avanza hacia la estandarización. Desde entonces, esto se ha estandarizado en ES2018.

  • Literal, no dinámico.


Object.assign()

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

Ventajas:

  • Estandarizado.

  • Dinámica. Ejemplo:

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

Desventajas

  • Más detallado.
  • Si el código de autoría se ejecuta en entornos sin compatibilidad nativa, debe rellenarlo.

Este es el compromiso que me hizo preguntarme.

Eso no está directamente relacionado con lo que estás preguntando. Ese código no estaba usando Object.assign(), estaba usando el código de usuario ( object-assign) que hace lo mismo. Parece que compilan ese código con Babel (y lo agrupan con Webpack), que es de lo que estaba hablando: la sintaxis que puedes compilar. Aparentemente prefirieron eso a tener que incluirlo object-assigncomo una dependencia que iría en su construcción.

JMM
fuente
15
Vale la pena señalar que la extensión del reposo de objetos se ha movido a la etapa 3, por lo que probablemente se estandarizará en el futuro twitter.com/sebmarkbage/status/781564713750573056
williaster
11
@JMM No estoy seguro de ver "Más detallado". como una desventaja
DiverseAndRemote.com
66
@Omar, bueno, supongo que acabas de demostrar que es una opinión :) Si alguien no reconoce esa ventaja, entonces, si todo lo demás es igual, puede usarlo Object.assign(). O puede iterar manualmente sobre una matriz de objetos y sus propios accesorios asignándolos manualmente a un objetivo y hacerlo aún más detallado: P
JMM
77
Como @JMM mencionó, ahora está en el nodo de especificaciones ES2018.green/#ES2018-features-object-rest-spread-properties
Sebastien H.
2
"Cada byte cuenta" Eso es lo que minimizadores / uglify es para @yzorg
DiverseAndRemote.com
171

Para el objeto de referencia, el reposo / propagación se finaliza en ECMAScript 2018 como una etapa 4. La propuesta se puede encontrar aquí .

En su mayor parte, el restablecimiento y la propagación de objetos funcionan de la misma manera, la diferencia clave es que la propagación define las propiedades, mientras que Object.assign () las establece . Esto significa que Object.assign () activa los establecedores.

Vale la pena recordar que, aparte de esto, el objeto rest / spread 1: 1 se asigna a Object.assign () y actúa de manera diferente a la matriz (iterable) spread. Por ejemplo, cuando se extiende una matriz, los valores nulos se extienden. Sin embargo, el uso de objetos con valores nulos se extiende silenciosamente a nada.

Ejemplo de dispersión de matriz (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

Ejemplo de dispersión de objetos

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

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

Esto es coherente con cómo funcionaría Object.assign (), ambos excluyen silenciosamente el valor nulo sin error.

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

console.log(z); //{a: 1, b: 2}
tomhughes
fuente
10
Esta debería ser la respuesta seleccionada.
Evan Plaice
44
Esta debería ser la respuesta ... es el futuro ahora.
Zachary Abresch
1
Esta es la respuesta correcta. La principal diferencia es Object.assignusará 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
"¡El futuro es ahora!" - George Allen Mañana es demasiado tarde.
ruffin
1
La demostración de que nulo se maneja de manera diferente es "manzanas y naranjas", no una comparación significativa. En el caso de la matriz, nulo es un elemento de la matriz. En el caso de propagación, nulo es todo el objeto. La comparación correcta sería para que x tienen una propiedad nula : const x = {c: null};. En cuyo caso, que yo sepa, quisiéramos ver el comportamiento al igual que la matriz: //{a: 1, b: 2, c: null}.
ToolmakerSteve
39

Creo que una gran diferencia entre el operador de propagación y Object.assignque no parece mencionarse en las respuestas actuales es que el operador de propagación no copiará el prototipo del objeto de origen al objeto de destino. Si desea agregar propiedades a un objeto y no desea cambiar de qué instancia es, entonces deberá usarlo Object.assign. El siguiente ejemplo debería demostrar esto:

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
fuente
"no mantendrá el prototipo intacto" , seguramente lo hará, ya que no modifica nada. En su ejemplo, errorExtendedUsingAssign === errorpero errorExtendedUsingSpreades un objeto nuevo (y el prototipo no se copió).
maaartinus
2
@maaartinus Tienes razón, probablemente lo dije mal. Quise decir que el prototipo no está en el objeto copiado. Podría editar eso para ser más claro.
Sean Dawson
¿Es la siguiente forma de "clonar superficialmente" un objeto con su clase? let target = Object.create(source); Object.assign(target, source);
ToolmakerSteve
@ToolmakerSteve Sí, copiará todas las "propiedades propias" del objeto a través de las cuales efectivamente será un clon superficial. Ver: stackoverflow.com/questions/33692912/…
Sean Dawson
12

Como otros han mencionado, en este momento de la escritura, se Object.assign()requiere un polyfill y la propagación de objetos ...requiere algo de transpiling (y quizás un polyfill también) para funcionar.

Considera este código:

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

Ambos producen la misma salida.

Aquí está la salida de 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);

Este es mi entendimiento hasta ahora. Object.assign()en realidad está estandarizado, donde la dispersión de objetos ...aún no está El único problema es el soporte del navegador para el primero y en el futuro, el segundo también.

Juega con el código aquí

Espero que esto ayude.

Michael Giovanni Pumo
fuente
1
¡Gracias! Su código de muestra hace que la decisión sea realmente fácil para mi contexto. El transpilador (babel o mecanografiado) hace que el operador de propagación sea más compatible con los navegadores al incluir un pollyfill en el código en línea. Solo por interés, la versión transpilada de TypeScript es prácticamente la misma que Babel: typescriptlang.org/play/…
Mark Whitfeld
2
hmm ... ¿tus dos casos no producirían los mismos resultados? en el primer caso está copiando propiedades de un objeto a otro, y en el otro está creando un nuevo objeto. Object.assign devuelve el objetivo, por lo que en su primer caso objAss y newObjAss son iguales.
Kevin B
Agregar un nuevo primer parámetro de {}debería corregir la inconsistencia.
Kevin B
11

El operador de propagación de objetos (...) no funciona en los navegadores, porque todavía no forma parte de ninguna especificación de ES, solo una propuesta. La única opción es compilarlo con Babel (o algo similar).

Como puede ver, es solo azúcar sintáctico sobre Object.assign ({}).

Hasta donde puedo ver, estas son las diferencias importantes.

  • Object.assign funciona en la mayoría de los navegadores (sin compilar)
  • ... para objetos no está estandarizado
  • ... te protege de mutar accidentalmente el objeto
  • ... polyfill Object.assign en navegadores sin él
  • ... necesita menos código para expresar la misma idea
Karthick Kumar
fuente
34
Es sin azúcar sintáctica para Object.assign, como el operador de difusión siempre te dará un nuevo objeto.
MaxArt
44
En realidad, me sorprende que otras personas no enfaticen más la diferencia de mutabilidad. Piense en todas las horas de desarrollador perdidas depurando mutaciones accidentales con Object.assign
profundización
Esto ahora es compatible con la mayoría de los navegadores modernos (como con otros ES6): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991
11

Me gustaría resumir el estado de la función ES de "fusión de objetos distribuidos", en navegadores y en el ecosistema a través de herramientas.

Especificaciones

Navegadores: en Chrome, en SF, Firefox pronto (ver 60, IIUC)

  • La compatibilidad del navegador para las "propiedades de propagación" incluidas en Chrome 60 , incluido este escenario.
  • El soporte para este escenario NO funciona en Firefox actual (59), pero sí funciona en mi Firefox Developer Edition. Así que creo que se enviará en Firefox 60.
  • Safari: no probado, pero Kangax dice que funciona en Desktop Safari 11.1, pero no en SF 11
  • Safari de iOS: no probado, pero Kangax dice que funciona en iOS 11.3, pero no en iOS 11
  • aún no en Edge

Herramientas: Nodo 8.7, TS 2.1

Enlaces

Muestra de código (funciona como prueba de compatibilidad)

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 }

De nuevo: al momento de escribir este ejemplo, funciona sin transpilación en Chrome (60+), Firefox Developer Edition (versión preliminar de Firefox 60) y Node (8.7+).

¿Por qué responder?

Estoy escribiendo esto 2.5 años después de la pregunta original. Pero tenía la misma pregunta, y aquí es donde me envió Google. Soy un esclavo de la misión de SO de mejorar la larga cola.

Dado que esta es una expansión de la sintaxis de "dispersión de matriz", encontré que es muy difícil para google y difícil de encontrar en las tablas de compatibilidad. Lo más cercano que pude encontrar es Kangax "spread de propiedad" , pero esa prueba no tiene dos spreads en la misma expresión (no una fusión). Además, el nombre en las páginas de propuestas / borradores / estado del navegador usa "propagación de propiedad", pero me parece que fue un "primer director" al que llegó la comunidad después de las propuestas para usar la sintaxis de propagación para "fusión de objetos". (Lo que podría explicar por qué es tan difícil buscar en Google). Así que documento mi hallazgo aquí para que otros puedan ver, actualizar y compilar enlaces sobre esta característica específica. Espero que se ponga de moda. Ayude a difundir la noticia de que aterriza en las especificaciones y en los navegadores.

Por último, habría agregado esta información como comentario, pero no podría editarla sin romper la intención original de los autores. Específicamente, no puedo editar el comentario de @ ChillyPenguin sin que pierda su intención de corregir a @RichardSchulte. Pero años después, Richard resultó tener razón (en mi opinión). Así que escribo esta respuesta en su lugar, con la esperanza de que eventualmente gane fuerza sobre las respuestas antiguas (podría tomar años, pero de eso se trata el efecto de cola larga , después de todo).

yzorg
fuente
3
su sección "por qué responder" probablemente no sea necesaria
LocustHorde
@LocustHorde Tal vez podría mover el segundo párrafo (por qué este tema es tan difícil de google) a su propia sección. Entonces el resto podría caber en un comentario.
yzorg
8

NOTA: Spread NO es solo azúcar sintáctico alrededor de Object.assign. Operan de manera muy diferente detrás de escena.

Object.assign aplica setters a un nuevo objeto, Spread no. Además, el objeto debe ser iterable.

Copiar Use esto si necesita el valor del objeto tal como está en este momento, y no desea que ese valor refleje los cambios realizados por otros propietarios del objeto.

Úselo para crear una copia superficial del objeto, una buena práctica para establecer siempre propiedades inmutables para copiar, ya que las versiones mutables se pueden pasar a propiedades inmutables, la copia garantizará que siempre esté tratando con un objeto inmutable

Asignar Asignar es algo opuesto a copiar. Asignar generará un setter que asigna el valor a la variable de instancia directamente, en lugar de copiarlo o retenerlo. Al llamar al captador de una propiedad de asignación, devuelve una referencia a los datos reales.

Charles Owen
fuente
3
¿Por qué dice "Copiar"? ¿Cuáles son estos títulos en negrita? Siento que me perdí algo de contexto cuando leí esto ...
ADJenks
2

Otras respuestas son viejas, no se pudo obtener una buena respuesta.
El siguiente ejemplo es para literales de objeto, ayuda a que ambos puedan complementarse entre sí y cómo no pueden complementarse entre sí (por lo tanto, la diferencia):

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

Varios ejemplos más pequeños aquí, también para matriz y objeto:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Manohar Reddy Poreddy
fuente
1

Esto ahora es parte de ES6, por lo tanto, está estandarizado y también está documentado en MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

Es muy conveniente de usar y tiene mucho sentido junto con la desestructuración de objetos.

La ventaja restante que se menciona anteriormente es la capacidad dinámica de Object.assign (), sin embargo, esto es tan fácil como distribuir la matriz dentro de un objeto literal. En la salida compilada de babel, usa exactamente lo que se demuestra con Object.assign ()

Por lo tanto, la respuesta correcta sería usar la distribución de objetos, ya que ahora está estandarizada, se usa ampliamente (ver react, redux, etc.), es fácil de usar y tiene todas las características de Object.assign ()

Rikki Schulte
fuente
2
No, no es parte de ES6. El enlace que ha proporcionado se refiere al uso del operador de propagación solo en matrices . El uso del operador de propagación en los objetos es actualmente una propuesta de Etapa 2 (es decir, Borrador), como se explica en la respuesta de JMM.
ChillyPenguin
1
No creo haber visto una respuesta tan completamente basada en información falsa. Incluso un año después, eso no forma parte de las especificaciones de ES y no es compatible con la mayoría de los entornos.
3oceno
Chilly y 3o resultaron estar equivocados, Richard tiene razón. El soporte del navegador y el soporte de herramientas están todos aterrizando, pero tomó 1.5 años después de la respuesta de Richard. Vea mi nueva respuesta para obtener un resumen del soporte a partir de marzo de 2018.
yzorg
0

Me gustaría agregar este ejemplo simple cuando tienes que usar 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!

No puede quedar claro cuando usas JavaScript. Pero con TypeScript es más fácil si desea crear una instancia de alguna clase

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