Propagação de objeto vs. Object.assign

396

Digamos que eu tenho um options variável e quero definir algum valor padrão.

Qual é a vantagem / desvantagem dessas duas alternativas?

Usando propagação de objeto

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

Ou usando Object.assign

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

Esse é o commit que me fez pensar.

Olivier Tassinari
fonte
4
Bem, a primeira é uma nova sintaxe proposta e não faz parte do ES6, portanto depende de qual padrão você deseja aderir.
Loganfsmyth
5
Definir " melhor " (cuidado, não acabar com uma pergunta baseada opinião :-)
Amit
11
Também pode depender de como você deseja suportá-lo se estiver executando em ambientes sem suporte nativo. Sintaxe que você pode conseguir compilar. Um objeto ou método que você pode precisar preencher.
JMM
6
além dos problemas de compatibilidade, o Object.assign pode alterar o objeto original que é útil. propagação não pode.
pstanton
2
Para esclarecer o comentário de @ pstanton - object.assign pode modificar um objeto de destino existente (sobrescrever as propriedades da origem, deixando outras propriedades intactas); não toca no objeto de origem . Li pela primeira vez o seu "objeto original" como "objeto de origem", portanto, escrevi esta nota para qualquer pessoa que a interprete da mesma forma. :)
ToolmakerSteve

Respostas:

328

Isso não é necessariamente exaustivo.

Sintaxe de propagação

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

Vantagens:

  • Se estiver criando código para execução em ambientes sem suporte nativo, você poderá compilar essa sintaxe (em vez de usar um polyfill). (Com Babel, por exemplo.)

  • Menos detalhado.

Desvantagens:

  • Quando esta resposta foi originalmente escrita, era uma proposta , não padronizada. Ao usar propostas, considere o que você faria se escrever um código com ele agora e ele não for padronizado ou alterado à medida que avança para a padronização. Desde então, isso foi padronizado no ES2018.

  • Literal, não dinâmico.


Object.assign()

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

Vantagens:

  • Padronizado.

  • Dinâmico. Exemplo:

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

Desvantagens:

  • Mais detalhado.
  • Se estiver criando código para execução em ambientes sem suporte nativo, é necessário polyfill.

Esse é o commit que me fez pensar.

Isso não está diretamente relacionado ao que você está perguntando. Esse código não estava usando Object.assign(), estava usando o código do usuário ( object-assign) que faz a mesma coisa. Eles parecem estar compilando esse código com o Babel (e empacotando-o no Webpack), que era o que eu estava falando: a sintaxe que você pode compilar. Aparentemente, eles preferiram ter que incluir object-assigncomo uma dependência que entraria em sua construção.

JMM
fonte
15
Pode ser interessante notar que objeto resto propagação mudou-se para a fase 3, irá provavelmente ser padronizados no futuro twitter.com/sebmarkbage/status/781564713750573056
williaster
11
@JMM Não tenho certeza se vejo "Mais detalhado". como uma desvantagem.
DiverseAndRemote.com
6
@ Omar, bem, eu acho que você acabou de provar que é uma opinião :) Se alguém não reconhece essa vantagem, então sim, todos os demais sendo iguais, podem apenas usar Object.assign(). Ou você poderia manualmente iterar sobre uma matriz de objetos e seus próprios adereços atribuindo-los manualmente para um alvo e torná-lo ainda mais detalhado: P
JMM
7
como @JMM mencionada, é agora nas ES2018 especificação node.green/#ES2018-features-object-rest-spread-properties
Sebastien H.
2
"Cada byte conta" É o que são minimizadores / uglify para @yzorg
DiverseAndRemote.com
171

Para o objeto de referência, o descanso / propagação é finalizado no ECMAScript 2018 como um estágio 4. A proposta pode ser encontrada aqui .

Na maioria das vezes, a redefinição e a propagação de objetos funcionam da mesma maneira, a principal diferença é que a propagação define propriedades, enquanto Object.assign () as define . Isso significa que Object.assign () aciona setters.

Vale lembrar que, além disso, o objeto rest / spread 1: 1 mapeia para Object.assign () e age de maneira diferente para o spread (iterável) da matriz. Por exemplo, ao espalhar uma matriz, valores nulos são espalhados. No entanto, ao usar objetos espalhados, os valores nulos são silenciosamente espalhados para nada.

Exemplo de propagação de matriz (iterável)

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

Exemplo de propagação de objeto

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

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

Isso é consistente com o funcionamento de Object.assign (), ambos excluem silenciosamente o valor nulo sem erro.

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

console.log(z); //{a: 1, b: 2}
tomhughes
fonte
10
Deve ser a resposta selecionada.
Evan Plaice
4
Esta deve ser a resposta ... é o futuro agora.
Zachary Abresch
11
Esta é a resposta certa. O principal diferente é 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
11
"O futuro é agora!" - George Allen Amanhã é tarde demais.
Ruffin
11
A demonstração de nulo sendo tratado de maneira diferente é "maçãs e laranjas" - não uma comparação significativa. No caso da matriz, null é um elemento da matriz. No caso de propagação, null é o objeto inteiro. A comparação correta seria para x para ter uma propriedade nulo : const x = {c: null};. Nesse caso, AFAIK, veríamos comportamento assim como a matriz: //{a: 1, b: 2, c: null}.
Página
39

Eu acho que uma grande diferença entre o operador de propagação e Object.assignisso não parece ser mencionado nas respostas atuais é que o operador de propagação não copiará o protótipo do objeto de origem no objeto de destino. Se você deseja adicionar propriedades a um objeto e não deseja alterar de qual instância ele é, será necessário usá-lo Object.assign. O exemplo abaixo deve demonstrar isso:

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
fonte
"não manterá o protótipo intacto" - certamente o fará, pois não modifica nada. No seu exemplo,, errorExtendedUsingAssign === errormas errorExtendedUsingSpreadé um novo objeto (e o protótipo não foi copiado).
Maaartinus 03/10/19
2
@maaartinus Você está certo, eu provavelmente falei mal disso. Eu quis dizer que o protótipo não está no objeto copiado. Eu posso editar isso para ficar mais claro.
Sean Dawson
A seguinte é uma maneira de "clonar superficialmente" um objeto com sua classe? let target = Object.create(source); Object.assign(target, source);
Página
@ToolmakerSteve Sim, ele copiará todas as "próprias propriedades" do objeto, através das quais será efetivamente um clone superficial. Veja: stackoverflow.com/questions/33692912/…
Sean Dawson
12

Como outros já mencionaram, neste momento da escrita, é Object.assign()necessário um polyfill e a propagação do objeto ...exige algumas transpilações (e talvez um polyfill também) para funcionar.

Considere 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 produzem a mesma saída.

Aqui está a saída de Babel, para 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 é o meu entendimento até agora. Object.assign()é realmente padronizado, onde, como a propagação do objeto ...ainda não é. O único problema é o suporte do navegador para o primeiro e, no futuro, para o último também.

Jogue com o código aqui

Espero que isto ajude.

Michael Giovanni Pumo
fonte
11
Obrigado! Seu exemplo de código facilita muito a decisão no meu contexto. O transpiler (babel ou datilografado) torna o operador de propagação mais compatível com os navegadores, incluindo um preenchimento de polly no código embutido. Apenas por interesse, a versão transpilada do TypeScript é praticamente a mesma de Babel: typescriptlang.org/play/…
Mark Whitfeld
2
hmm ... seus dois casos não produziriam os mesmos resultados? no primeiro caso, você está copiando propriedades de um objeto para outro e, no outro, está criando um novo objeto. Object.assign retorna o destino, portanto, no seu primeiro caso, objAss e newObjAss são os mesmos.
Kevin B
A adição de um novo primeiro parâmetro de {}deve corrigir a inconsistência.
Kevin B
11

O operador de propagação de objetos (...) não funciona nos navegadores, porque ainda não faz parte de nenhuma especificação de ES, apenas uma proposta. A única opção é compilá-lo com Babel (ou algo semelhante).

Como você pode ver, é apenas açúcar sintático sobre Object.assign ({}).

Tanto quanto eu posso ver, essas são as diferenças importantes.

  • Object.assign funciona na maioria dos navegadores (sem compilar)
  • ... para objetos não é padronizado
  • ... protege você de alterar acidentalmente o objeto
  • ... polyfill Object.assign em navegadores sem ele
  • ... precisa de menos código para expressar a mesma ideia
Karthick Kumar
fonte
34
Não é um açúcar sintático Object.assign, pois o operador de propagação sempre fornecerá um novo objeto.
MaxArt
4
Na verdade, estou surpreso que outras pessoas não estejam enfatizando mais a diferença de mutabilidade. Pense em todas as horas do desenvolvedor perdidas na depuração de mutações acidentais com o Object.assign
#
Agora isso é suportado na maioria dos navegadores modernos (como em outros ES6): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
akmalhakimi1991
11

Eu gostaria de resumir o status do recurso ES de "propagação de mesclagem de objetos", em navegadores e no ecossistema por meio de ferramentas.

Spec

Navegadores: no Chrome, no SF, no Firefox em breve (versão 60, IIUC)

  • Suporte do navegador para "propriedades de propagação" enviado no Chrome 60 , incluindo este cenário.
  • O suporte para esse cenário NÃO funciona no Firefox atual (59), mas funciona no meu Firefox Developer Edition. Então, eu acredito que ele será lançado no Firefox 60.
  • Safari: não testado, mas Kangax diz que funciona no Desktop Safari 11.1, mas não no SF 11
  • iOS Safari: não é testado, mas Kangax diz que funciona no iOS 11.3, mas não no iOS 11
  • ainda não está no Edge

Ferramentas: Nó 8.7, TS 2.1

Ligações

Amostra de código (funciona como teste de compatibilidade)

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 }

Novamente: no momento da redação deste exemplo, funciona sem transpilação no Chrome (60+), Firefox Developer Edition (visualização do Firefox 60) e Node (8.7+).

Por que responder?

Estou escrevendo isso 2,5 anos após a pergunta original. Mas eu tinha a mesma pergunta, e foi aqui que o Google me enviou. Sou escravo da missão da SO de melhorar a cauda longa.

Como essa é uma expansão da sintaxe "array spread", achei muito difícil para o google e difícil de encontrar em tabelas de compatibilidade. O mais próximo que eu pude encontrar é Kangax "propriedade espalhada" , mas esse teste não tem dois spreads na mesma expressão (não é uma mesclagem). Além disso, o nome nas páginas de propostas / rascunhos / status do navegador usa "propriedade espalhada", mas me parece que esse foi o "primeiro principal" ao qual a comunidade chegou depois das propostas para usar a sintaxe de propagação para "mesclagem de objetos". (O que pode explicar por que é tão difícil pesquisar no Google.) Portanto, documento minha descoberta aqui para que outras pessoas possam visualizar, atualizar e compilar links sobre esse recurso específico. Espero que capte. Ajude a espalhar as notícias sobre o desembarque nas especificações e nos navegadores.

Por fim, eu adicionaria essas informações como um comentário, mas não poderia editá-las sem quebrar a intenção original dos autores. Especificamente, não posso editar o comentário de @ ChillyPenguin sem que ele perca a intenção de corrigir @RichardSchulte. Mas anos depois, Richard se mostrou certo (na minha opinião). Então, em vez disso, escrevo esta resposta, esperando que, eventualmente, ganhe força nas respostas antigas (pode levar anos, mas afinal é isso o efeito da cauda longa ).

yzorg
fonte
3
sua seção "por que responder" provavelmente não é necessária?
LocustHorde 28/09
@LocustHorde Talvez eu possa mover o segundo parágrafo (por que esse tópico é tão difícil de pesquisar no Google) para sua própria seção. O resto pode caber em um comentário.
yzorg 28/09
8

NOTA: A propagação NÃO é apenas açúcar sintático em torno do Object.assign. Eles operam de maneira muito diferente nos bastidores.

Object.assign aplica setters a um novo objeto, o Spread não. Além disso, o objeto deve ser iterável.

Copiar Use isso se você precisar do valor do objeto como está neste momento e não desejar que esse valor reflita as alterações feitas por outros proprietários do objeto.

Use-o para criar uma cópia superficial das boas práticas do objeto para sempre definir propriedades imutáveis ​​para copiar - como versões mutáveis ​​podem ser passadas para propriedades imutáveis, a cópia garantirá que você sempre lide com um objeto imutável

Atribuir Atribuir é um pouco o oposto de copiar. Atribuir irá gerar um setter que atribui o valor diretamente à variável de instância, em vez de copiá-lo ou retê-lo. Ao chamar o getter de uma propriedade de atribuição, ele retorna uma referência aos dados reais.

Charles Owen
fonte
3
Por que diz "Copiar"? Quais são esses títulos em negrito. Eu sinto como se eu perdi algum contexto quando li isso ...
ADJenks
2

Outras respostas são antigas, não foi possível obter uma boa resposta.
O exemplo abaixo é para literais de objetos, ajuda como ambos podem se complementar e como eles não podem se complementar (portanto, diferença):

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

Vários outros exemplos pequenos aqui, também para array e objeto:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Manohar Reddy Poreddy
fonte
1

Agora isso faz parte do ES6, portanto é padronizado e também está documentado no MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator

É muito conveniente de usar e faz muito sentido, além da destruição de objetos.

A única vantagem restante listada acima são os recursos dinâmicos de Object.assign (), no entanto, isso é tão fácil quanto espalhar a matriz dentro de um objeto literal. Na saída babel compilada, ele usa exatamente o que é demonstrado com Object.assign ()

Portanto, a resposta correta seria usar a propagação de objetos, uma vez que agora é padronizada, amplamente utilizada (consulte react, redux, etc), é fácil de usar e possui todos os recursos de Object.assign ()

Rikki Schulte
fonte
2
Não, não faz parte do ES6. O link que você forneceu refere-se ao uso do operador de propagação apenas em matrizes . Atualmente, o uso do operador de propagação em objetos é uma proposta do Estágio 2 (por exemplo, Rascunho), conforme explicado na resposta do JMM.
ChillyPenguin
11
Acho que nunca vi uma resposta tão inteiramente baseada em informações falsas. Mesmo um ano depois, isso não faz parte da especificação do ES e não é suportado na maioria dos ambientes.
3ocene
Chilly e 3o estavam errados, Richard certo. O suporte a navegadores e a ferramentas estão chegando, mas levou 1,5 anos após a resposta de Richard. Veja minha nova resposta para obter um resumo do suporte a partir de março de 2018.
yzorg
0

Eu gostaria de adicionar este exemplo simples quando você precisar usar o 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!

Não pode ficar claro quando você usa JavaScript. Mas com o TypeScript é mais fácil se você deseja criar uma instância de alguma classe

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