JavaScript: como passar objeto por valor?

96
  • Ao passar objetos como parâmetros, o JavaScript os passa por referência e torna difícil criar cópias locais dos objetos.

    var o = {};
    (function(x){
        var obj = x;
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    oterá .fooe .bar.

  • É possível contornar isso clonando; exemplo simples:

    var o = {};
    
    function Clone(x) {
       for(p in x)
       this[p] = (typeof(x[p]) == 'object')? new Clone(x[p]) : x[p];
    }
    
    (function(x){
        var obj = new Clone(x);
        obj.foo = 'foo';
        obj.bar = 'bar';
    })(o)

    onão terá .fooou .bar.


Questão

  1. Existe uma maneira melhor de passar objetos por valor, além de criar uma cópia / clone local?
Vol7ron
fonte
3
Qual é o seu caso de uso para precisar disso?
jondavidjohn
2
Diversão de programação. Ver se os novos engines de JS trataram disso (tecnicamente, é passar a referência por valor), mas principalmente por diversão.
vol7ron
1
Consulte O JavaScript é uma linguagem passada por referência ou uma linguagem passada por valor? - JavaScript não passa por referência. Como Java, ao passar objetos para uma função, ele passa por valor, mas o valor é uma referência. Consulte também O Java é passado por referência? .
Richard JP Le Guen
1
Pelo que sei, você não pode passar objetos por valor. Mesmo se houvesse, na verdade estaria fazendo a clonagem a que você se refere acima, portanto, não há nada a ganhar que eu possa ver. Além de talvez salvar 3 linhas de código.
Thor84no
1
@ vol7ron Sim, eles resolveram isso implementando uma característica de design de linguagem corretamente.
jondavidjohn

Respostas:

61

Na verdade não.

Dependendo do que você realmente precisa, uma possibilidade pode ser definir ocomo o protótipo de um novo objeto.

var o = {};
(function(x){
    var obj = Object.create( x );
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o);

alert( o.foo ); // undefined

Portanto, quaisquer propriedades adicionadas objnão serão adicionadas o. Todas as propriedades adicionadas objcom o mesmo nome de propriedade de uma propriedade em oirão sombrear a opropriedade.

Obviamente, todas as propriedades adicionadas a oestarão disponíveis objse não estiverem sombreadas, e todos os objetos que estão ona cadeia de protótipos verão as mesmas atualizações em o.

Além disso, se objtem uma propriedade que faz referência a outro objeto, como um Array, você precisará ter certeza de sombrear esse objeto antes de adicionar membros ao objeto, caso contrário, esses membros serão adicionados obje serão compartilhados entre todos os objetos que tem objna cadeia de protótipos.

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // 'new_value'

Aqui você pode ver que porque você não sombra da matriz em bazon ocom uma bazpropriedade em obj, a o.bazmatriz é modificado.

Então, em vez disso, você precisa sombrear primeiro:

var o = {
    baz: []
};
(function(x){
    var obj = Object.create( x );

    obj.baz = [];
    obj.baz.push( 'new value' );

})(o);

alert( o.baz[0] );  // undefined
user113716
fonte
3
+1, eu ia sugerir Object.createna minha resposta também, mas não quis me esticar para explicar a superficialidade da cópia ;-)
Andy E
1, eu esqueci disso. Eu tentei var obj = new Object(x). Para mim, eu acho que new Object(x)teria um desempenho Object.create(x)padrão - interessante
vol7ron de
1
@ vol7ron: Sim, new Object(x)apenas cospe de volta o mesmo objeto (como você provavelmente percebeu) . Seria bom se houvesse uma maneira nativa de clonar. Não tenho certeza se há algo em andamento.
user113716
43

Confira esta resposta https://stackoverflow.com/a/5344074/746491 .

Resumindo, JSON.parse(JSON.stringify(obj))é uma maneira rápida de copiar seus objetos, se seus objetos puderem ser serializados para json.

svimre
fonte
2
Eu tendo a evitar isso porque os objetos nem sempre podem ser serializados e porque em navegadores de meia idade como o IE7 / 8, JSONnão está incluído e precisa ser definido - pode muito bem ser mais descritivo com um nome como Clone. No entanto, suspeito que minha opinião pode mudar no futuro, uma vez que JSON é mais fortemente integrado em software não baseado em navegador, como bancos de dados e IDEs
vol7ron
1
Se não houver funções e objetos de data, a solução JSON.parse parece ser a melhor. stackoverflow.com/questions/122102/…
Herr_Hansen
Realmente?!? Serializar para JSON apenas para criar uma cópia do objeto? Mais uma razão para odiar essa linguagem absurda.
RyanNerd
1
Serializar JSON é realmente uma maneira rápida de copiá-lo? De acordo com a documentação do NodeJS, a manipulação de JSON é a tarefa mais intensiva da CPU. nodejs.org/en/docs/guides/dont-block-the-event-loop/…
harshad
38

Aqui está a função de clone que realizará uma cópia profunda do objeto:

function clone(obj){
    if(obj == null || typeof(obj) != 'object')
        return obj;

    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);

    return temp;
}

Agora você pode usar assim:

(function(x){
    var obj = clone(x);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(o)
Paul Varghese
fonte
3
Esta foi uma resposta profunda.
Nathan
Lindo! Melhor resposta IMO.
ganders
23

Usar Object.assign()

Exemplo:

var a = {some: object};
var b = new Object;
Object.assign(b, a);
// b now equals a, but not by association.

Um exemplo mais limpo que faz a mesma coisa:

var a = {some: object};
var b = Object.assign({}, a);
// Once again, b now equals a.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Brandon
fonte
4
Melhor:var b = Object.assign({}, a);
Bergi,
Acordado. Atualizando a resposta de acordo. Além disso, nem todo navegador oferece suporte ao método assign (). Dependendo dos seus requisitos de compatibilidade, pode ser necessário usar um polyfill como o do MDN.
Brandon
3
@Brandon, no próprio link que você forneceu, ele dá um aviso para clonagem profunda: "Para clonagem profunda, precisamos usar outras alternativas porque Object.assign () copia valores de propriedade. Se o valor de origem é uma referência a um objeto, ele apenas copia aquele valor de referência. "
pwilcox
2
Isso funciona, mas lembre-se de que não executa uma cópia profunda, nenhum objeto contido dentro dele aserá copiado como referência.
Stoinov
15

Você está um pouco confuso sobre como os objetos funcionam em JavaScript. A referência do objeto é o valor da variável. Não há valor não serializado. Quando você cria um objeto, sua estrutura é armazenada na memória e a variável à qual foi atribuída contém uma referência a essa estrutura.

Mesmo se o que você está pedindo foi fornecido em algum tipo de construção de linguagem nativa fácil, ainda seria tecnicamente uma clonagem.

JavaScript é realmente apenas passado por valor ... é apenas que o valor passado pode ser uma referência a algo.

Andy E
fonte
1
Ei AndyE! Não estou confuso com isso, estava esperançoso de que JS faria a clonagem por padrão. Existem grandes ineficiências na autoclonagem, então, se o motor fizesse isso, tenho certeza de que seria melhor. Duvido que a necessidade / demanda exceda o benefício, no entanto.
vol7ron
12
Em JS, quando as pessoas dizem por referência em vez de por valor, elas significam que o valor é como um ponteiro em vez de uma duplicata. Essa é uma linguagem bem estabelecida.
Erik Reppen
4
@Erik: na minha humilde opinião, acho melhor defini-lo com mais clareza para não confundir os novatos com o idioma. Só porque algo está bem estabelecido não significa que seja apropriado ou mesmo tecnicamente correto (porque não é). As pessoas dizem "por referência" porque é mais fácil para elas dizer isso do que explicar como realmente funciona.
Andy E
6
O valor é uma referência a um local na memória. No final das contas, você ainda está agindo no mesmo item na memória, em vez de trabalhar com uma cópia. Como fazer essa distinção ambígua pode ajudar os recém-chegados? É assim que é explicado em muitos livros de tecnologia, incluindo o Guia oficial da O'Reilly, que está agora em sua quinta edição (OT: e com atualização muito fraca).
Erik Reppen
1
@AndyE eu li um pouco mais para ver o que estava acontecendo. IMO, isso é como a questão de saber se JS tem OOP "verdadeiro" ou podemos chamar algo de "método" apropriadamente em comparação com outras linguagens. Não estou realmente certo de que seria viável tratar vars como gravando diretamente no mesmo objeto na memória, em vez de sobrescrever seu ponteiro copiado em uma linguagem baseada em fechamento com ramificação aniquiladora de desempenho do comportamento do operador de atribuição. A única diferença de comportamento real no que queremos dizer em JS versus outras linguagens é o que acontece quando você usa o operador de atribuição.
Erik Reppen
15

Usa isto

x = Object.create(x1);

xe x1serão dois objetos diferentes, a mudança xnão mudaráx1

user5242529
fonte
Acabo usando Object.assign (). Para um caso de teste simples, .create () funciona, mas para os mais complexos (que ainda não consigo encontrar o padrão), ele ainda se refere ao mesmo endereço.
Coisox
Ele não mudará apenas se os valores do objeto não forem armazenados por referência, ou seja, eles são tipos de dados premitivos como número ou booleano. Por exemplo, considere o código abaixo a = {x: [12], y: []}; b = Object.create (a); bxpush (12); console.log (machado);
Jjagwe Dennis
8

Javascript sempre passa por valor. Neste caso está passando uma cópia da referênciao para a função anônima. O código está usando uma cópia da referência, mas está alterando o único objeto. Não há como fazer o javascript passar por qualquer coisa que não seja valor.

Neste caso, o que você deseja é passar uma cópia do objeto subjacente. Clonar o objeto é o único recurso. Seu método de clone precisa de uma pequena atualização embora

function ShallowCopy(o) {
  var copy = Object.create(o);
  for (prop in o) {
    if (o.hasOwnProperty(prop)) {
      copy[prop] = o[prop];
    }
  }
  return copy;
}
JaredPar
fonte
1
+1 Para "sempre passa por valor", embora eu prefira usar Chamada por Compartilhamento de Objeto (em oposição a "Chamada por Valor [da Referência]") e promova "o valor" para "o próprio objeto" (com a observação de que o objeto não é copiado / clonado / duplicado), pois as referências são apenas um detalhe de implementação e não são expostas ao JavaScript (ou diretamente mencionadas na especificação!).
Tive um problema ao adicionar propriedades a um objeto que estou recebendo de uma conexão remota (pensei que poderia ser o problema de não ser capaz de adicionar nenhuma propriedade a esse objeto), então tentei esta função de cópia superficial e obtive este erro - Protótipo de objeto pode ser apenas um objeto ou nulo em var copy = Object.create (o); alguma ideia de como resolver meu problema
Harshit Laddha
Esta não é uma cópia profunda?
River Tam,
@RiverTam Não, não é uma cópia profunda, porque não clona os objetos dentro dos objetos. No entanto, você pode facilmente torná-la uma cópia profunda apenas fazendo com que ela se auto-denomine recursivamente em cada um dos subobjetos.
ABPerson,
7

Como consideração aos usuários do jQuery, também há uma maneira de fazer isso de maneira simples usando o framework. Apenas outra maneira que o jQuery torna nossas vidas um pouco mais fáceis.

var oShallowCopy = jQuery.extend({}, o);
var oDeepCopy    = jQuery.extend(true, {}, o); 

referências :

Brett Weber
fonte
Ah - você descobriu esta velha questão :) Acho que a questão original era mais uma exploração da linguagem JS. Parece que até minha terminologia era fraca naquela época, já que meu exemplo era de uma cópia profunda em vez de um clone . Mas parece que objetos / hashes só podem ser passados ​​por referência, o que é comum em linguagens de script
vol7ron
1
:) Deparei com ele procurando a funcionalidade exata. Eu tinha um objeto de dados que precisava copiar para edição antes de enviar enquanto persistia o objeto original. Eu prefiro a solução JS nativa e irei usá-la em minha própria biblioteca base, mas a solução jQuery é minha correção temporária. A resposta aceita é fantástica. : D
Brett Weber,
6

Na verdade, Javascript é sempre passado por valor . Mas porque as referências de objeto são valores , os objetos se comportarão como se fossem passados ​​por referência .

Portanto, para contornar isso, stringifique o objeto e analise -o de volta, ambos usando JSON. Veja o exemplo de código abaixo:

var person = { Name: 'John', Age: '21', Gender: 'Male' };

var holder = JSON.stringify(person);
// value of holder is "{"Name":"John","Age":"21","Gender":"Male"}"
// note that holder is a new string object

var person_copy = JSON.parse(holder);
// value of person_copy is { Name: 'John', Age: '21', Gender: 'Male' };
// person and person_copy now have the same properties and data
// but are referencing two different objects
Abubakar Ahmad
fonte
2
Sim, esta é uma opção para conversão / reconversão simples. Geralmente, trabalhar com strings é muito ineficiente; entretanto, não avaliei o desempenho da análise JSON há algum tempo. Lembre-se de que, embora isso funcione para objetos simples, os objetos que contêm funções como valores perderão conteúdo.
vol7ron
4

use obj2 = {... obj1} Agora, ambos os objetos têm o mesmo valor e referências diferentes

Geetanshu Gulati
fonte
2
Novo em javascript aqui. O operador Spread cria um novo valor em vez de copiar por referência? Então, por que isso foi rejeitado?
jo3birdtalk
2
À primeira vista, funciona para mim. Mas descubro que é apenas para a primeira camada da variável. Por exemplo, ele determina quando let a = {value: "old"} let b = [...a] b.value = "new", e a será {value: "old"}, b será {value: "new"}. Mas não funciona quando let a = [{value: "old"}] let b = [...a] b[0].value = "new", e a será [{value: "new"}], b será [{value: "new"}].
Brady Huang
sim, ele faz uma cópia superficial. Se você tem um objeto aninhado, você precisa fazer uma cópia profunda. Para simplificar, você pode usar as funções clone e deepClone do lodash
Geetanshu Gulati
3

Eu precisava copiar um objeto por valor (não por referência) e achei esta página útil:

Qual é a maneira mais eficiente de clonar profundamente um objeto em JavaScript? . Em particular, clonar um objeto com o seguinte código de John Resig:

//Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
Anthony Haffey
fonte
A questão é sobre JavaScript, não sobre a biblioteca jQuery.
j08691
1
Verdade, para pessoas que não podem usar Jquery por qualquer motivo, isso não os ajudaria. No entanto, como Jquery é uma biblioteca javascript, acho que ainda ajudará a maioria dos usuários de javascript.
Anthony Haffey de
Não vai ajudar nenhum dos programadores a tentar fazê-lo no servidor. Então não. Não é uma resposta válida.
Tomasz Gałkowski
3

Com a sintaxe ES6:

let obj = Object.assign({}, o);

Tomasz Gałkowski
fonte
3
Muito boa menção ao @galkowskit. O maior problema (melhor: uma coisa a se notar ) com o Object.assigné que ele não executa uma cópia profunda.
vol7ron
1

No final das contas, é apenas um proxy extravagante e excessivamente complicado, mas talvez Catch-All Proxies pudesse fazer isso?

var o = {
    a: 'a',
    b: 'b',
    func: function() { return 'func'; }
};

var proxy = Proxy.create(handlerMaker(o), o);

(function(x){
    var obj = x;
    console.log(x.a);
    console.log(x.b);
    obj.foo = 'foo';
    obj.bar = 'bar';
})(proxy);

console.log(o.foo);

function handlerMaker(obj) {
  return {
   getOwnPropertyDescriptor: function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name);
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getPropertyDescriptor:  function(name) {
     var desc = Object.getOwnPropertyDescriptor(obj, name); // not in ES5
     // a trapping proxy's properties must always be configurable
     if (desc !== undefined) { desc.configurable = true; }
     return desc;
   },
   getOwnPropertyNames: function() {
     return Object.getOwnPropertyNames(obj);
   },
   getPropertyNames: function() {
     return Object.getPropertyNames(obj);                // not in ES5
   },
   defineProperty: function(name, desc) {

   },
   delete:       function(name) { return delete obj[name]; },   
   fix:          function() {}
  };
}
Richard JP Le Guen
fonte
Richard, estou um pouco atrasado para responder :) mas acho que nunca respondi por alguns motivos: estava procurando no açúcar sintático que deu a JS a capacidade de passar objetos de maneiras diferentes; sua resposta parecia longa e eu estava obcecado pelo uso de "proxy". 3 anos depois e ainda estou preso ao uso. Talvez eu não tenha prestado atenção suficiente nas aulas, mas, assim como a rede, pensei que os proxies no CS deveriam atuar como intermediários. Talvez esteja aqui também e não estou pensando corretamente sobre o que é uma mediação entre.
vol7ron
0

Se você estiver usando Lodash ou npm, use a função de mesclagem de Lodash para copiar em profundidade todas as propriedades do objeto para um novo objeto vazio como:

var objectCopy = lodash.merge({}, originalObject);

https://lodash.com/docs#merge

https://www.npmjs.com/package/lodash.merge

Con Antonakos
fonte
Uma cópia profunda também deve incluir eventos, não estou muito familiarizado com lodash, ela espelha eventos para um novo objeto?
vol7ron de