Nodejs: como clonar um objeto

90

Se eu clonar um array, uso cloneArr = arr.slice()

Eu quero saber como clonar um objeto em nodejs.

guilin 桂林
fonte

Respostas:

175

Para utilitários e classes onde não há necessidade de espremer cada gota de desempenho, geralmente eu trapaceio e apenas uso JSON para realizar uma cópia profunda:

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

Esta não é a única resposta ou a resposta mais elegante; todas as outras respostas devem ser consideradas para gargalos de produção. No entanto, essa é uma solução rápida e suja, bastante eficaz e útil na maioria das situações em que eu clonaria um simples hash de propriedades.

Kato
fonte
2
@djechlin Claro que sim. Experimente: jsfiddle.net/katowulf/E5jC3 (testado com o nó 0.10.11) Não será capaz de reconstituir funções ou dados prototípicos, mas obtém os valores perfeitamente.
Kato
11
Isso converterá as datas em strings
backus
4
@Backus Bem como objetos e funções.
Kato
2
@Kato, por que referências circulares implicariam que não há necessidade de fazer cópias profundas? São duas coisas não relacionadas. Certamente é possível escrever um bom método de clonagem para NodeJS que suporte referências circulares e não use JSON para clonar. github.com/pvorb/node-clone
Samuel Neff
2
Já anotado nos comentários e na descrição. Você não pode clonar uma função, protótipo de objeto ou outras coisas que não deveria tentar fazer com um método de clonagem genérico. Você precisará de mais de uma linha de código para isso, e provavelmente não deveria clonar aquele lixo de qualquer maneira - coloque um método clone em sua classe que saiba como lidar com os internos e façanewObj = obj.clone(args...);
Kato
34

Object.assign não foi mencionado em nenhuma das respostas acima.

let cloned = Object.assign({}, source);

Se você estiver no ES6, pode usar o operador de propagação:

let cloned = { ... source };

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

Sam G
fonte
28
Observe que ambos são clones superficiais e o operador de propagação em objetos requer node.js 8+.
jlh
1
Como @jlh mencionou, isso não vai funcionar para objetos aninhados. Você pode ler mais sobre questões profundas de clonagem desse método aqui: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Shnd
Como disse Shnd, essa resposta está incorreta. Para clonagem profunda, precisamos usar outras alternativas porque Object.assign () copia os valores das propriedades. Se o valor de origem for uma referência a um objeto, ele apenas copia esse valor de referência.
troy de
1
Isso não clona, ​​apenas faz referência ao objeto original, ou seja, se você alterar algo, ele irá para o mestre
Aidan Welch
@AidanWelch Não acho isso correto. let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Miguel Pynto
20

Existem alguns módulos Node lá fora, se você não quiser "lançar o seu próprio". Este parece bom: https://www.npmjs.com/package/clone

Parece que ele lida com todos os tipos de coisas, incluindo referências circulares. Na página do github :

clone masters clonando objetos, matrizes, objetos Date e objetos RegEx. Tudo é clonado recursivamente, para que você possa clonar datas em arrays em objetos, por exemplo. [...] Referências circulares? Sim!

Clint Harris
fonte
10

É difícil fazer uma operação de clonagem genérica, mas útil, porque o que deve ser clonado recursivamente e o que deve ser apenas copiado depende de como o objeto específico deve funcionar.

Algo que pode ser útil é

function clone(x)
{
    if (x === null || x === undefined)
        return x;
    if (typeof x.clone === "function")
        return x.clone();
    if (x.constructor == Array)
    {
        var r = [];
        for (var i=0,n=x.length; i<n; i++)
            r.push(clone(x[i]));
        return r;
    }
    return x;
}

Neste código, a lógica é

  • no caso de nullou undefinedapenas retornar o mesmo (o caso especial é necessário porque é um erro tentar ver se um clonemétodo está presente)
  • o objeto tem um clone método? então use isso
  • é o objeto um array? em seguida, faça uma operação de clonagem recursiva
  • caso contrário, apenas retorne o mesmo valor

Esta função de clone deve permitir a implementação de métodos de clone personalizados facilmente ... por exemplo

function Point(x, y)
{
    this.x = x;
    this.y = y;

    ...
}

Point.prototype.clone = function()
{
    return new Point(this.x, this.y);
};



function Polygon(points, style)
{
    this.points = points;
    this.style = style;

    ...
}

Polygon.prototype.clone = function()
{
    return new Polygon(clone(this.points),
                       this.style);
};

Quando no objeto você sabe que uma operação de clonagem correta para um array específico é apenas uma cópia superficial, você pode chamar em values.slice()vez de clone(values).

Por exemplo, no código acima, estou exigindo explicitamente que a clonagem de um objeto de polígono clonará os pontos, mas compartilhará o mesmo objeto de estilo. Se eu quiser clonar o objeto de estilo também, posso simplesmente passar clone(this.style).

6502
fonte
1
1 por mencionar que os objetos precisam implementar .clonemétodos próprios. Esta é a melhor maneira de lidar com a clonagem de objetos.
Raynos
3
if (x.clone)deveria serif (typeof x.clone === 'function')
Yanick Rochon
@YanickRochon: Obrigado, consertado. Desculpe, de alguma forma eu não percebi esse comentário antes ...
6502
9

Não existe um método nativo para clonar objetos. Implementos sublinhados, _.cloneque é um clone raso.

_.clone = function(obj) {
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

Ele o corta ou o estende.

Aqui está _.extend

// extend the obj (first parameter)
_.extend = function(obj) {
  // for each other parameter
  each(slice.call(arguments, 1), function(source) {
    // loop through all properties of the other objects
    for (var prop in source) {
      // if the property is not undefined then add it to the object.
      if (source[prop] !== void 0) obj[prop] = source[prop];
    }
  });
  // return the object (first parameter)
  return obj;
};

Extend simplesmente itera por todos os itens e cria um novo objeto com os itens nele.

Você pode lançar sua própria implementação ingênua se quiser

function clone(o) {
  var ret = {};
  Object.keys(o).forEach(function (val) {
    ret[val] = o[val];
  });
  return ret;
}

Existem boas razões para evitar a clonagem profunda, pois os fechamentos não podem ser clonados.

Eu pessoalmente fiz uma pergunta sobre deep cloning objects beforee a conclusão a que cheguei é que você simplesmente não faz isso.

Minha recomendação é usar underscoree seu _.clonemétodo para clones rasos

Raynos
fonte
9

Para uma cópia superficial, gosto de usar o padrão de redução (geralmente em um módulo ou algo assim), assim:

var newObject = Object.keys(original).reduce(function (obj, item) {
    obj[item] = original[item];
    return obj;
},{});

Aqui está um jsperf para algumas das opções: http://jsperf.com/shallow-copying

Olli K
fonte
simples, elegante, brilhante. Estou ansioso para suas outras contribuições SO ^ _ ^
Obrigado
7

Pergunta antiga, mas há uma resposta mais elegante do que a que foi sugerida até agora; use os utils._extend integrados:

var extend = require("util")._extend;

var varToCopy = { test: 12345, nested: { val: 6789 } };

var copiedObject = extend({}, varToCopy);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 } }

Observe o uso do primeiro parâmetro com um objeto vazio {} - isso diz a extend que o (s) objeto (s) copiado (s) precisam ser copiados para um novo objeto. Se você usar um objeto existente como o primeiro parâmetro, o segundo (e todos os subsequentes) parâmetros serão copiados para mesclagem profunda sobre a primeira variável de parâmetro.

Usando as variáveis ​​de exemplo acima, você também pode fazer isso:

var anotherMergeVar = { foo: "bar" };

extend(copiedObject, { anotherParam: 'value' }, anotherMergeVar);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 }, anotherParam: 'value', foo: 'bar' }

Utilitário muito útil, especialmente onde estou acostumado a estender em AngularJS e jQuery.

Espero que isso ajude outra pessoa; as sobregravações de referência de objeto são uma miséria e isso sempre resolve o problema!

Scott Byers
fonte
1
Usar _extend agora está depreciado. Use Object.assign () em vez disso: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MacroMan
7

Você também pode usar o lodash . Possui métodos clone e cloneDeep .

var _= require('lodash');

var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
Michal
fonte
1

Dependendo do que você deseja fazer com seu objeto clonado, você pode utilizar o mecanismo de herança prototípico de javascript e obter um objeto clonado através de:

var clonedObject = Object.create(originalObject);

Lembre-se de que este não é um clone completo - para o bem ou para o mal.

Uma coisa boa sobre isso é que você realmente não duplicou o objeto, então a área de cobertura da memória será baixa.

No entanto, algumas coisas difíceis de lembrar sobre este método são que a iteração de propriedades definidas na cadeia de protótipos às vezes funciona um pouco diferente e o fato de que quaisquer alterações no objeto original afetarão o objeto clonado também, a menos que essa propriedade também tenha sido definida em si mesma .

VoxPelli
fonte
3
Isso é tão ridículo, perigoso e totalmente diferente de um clone. var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
Obrigado
@naomik: b.foo = "working"; console.log(a.foo); // still "broken";É preciso estar ciente de que as alterações no objeto original serão refletidas no "clone" e que as alterações no "clone" não serão refletidas no objeto original - mas não o chamaria de perigoso - tudo depende do que você quer fazer com seu clone
VoxPelli
@naomik: Sou muito novo em JS, então gostaria de algumas precisões. O clone raso proposto por olli-k e que você gostou parece (IMHO, mas posso estar errado) ter as mesmas limitações que a solução proposta por voxpelli. O fechamento original e raso também compartilharão os mesmos "itens", atualizar um item no original também terá impacto no clone raso. Ou?
bmorin
@bmornin, se você usar a técnica de Olli K, modificar o objeto original não causará uma mudança no novo objeto.
Obrigado
1
@VoxPelli Todo o propósito de um "clone" é isolar as alterações do objeto original, portanto, esse método é mais perigoso.
Doug
1

Implementei uma cópia profunda completa. Acredito que seja a melhor escolha para um método de clone genérico, mas não lida com referências cíclicas.

Exemplo de uso:

parent = {'prop_chain':3}
obj = Object.create(parent)
obj.a=0; obj.b=1; obj.c=2;

obj2 = copy(obj)

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3
console.log(obj2, obj2.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3

parent.prop_chain=4
obj2.a = 15

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 4
console.log(obj2, obj2.prop_chain)
// '{'a':15, 'b':1, 'c':2} 4

O próprio código:

Este código copia objetos com seus protótipos, ele também copia funções (pode ser útil para alguém).

function copy(obj) {
  // (F.prototype will hold the object prototype chain)
  function F() {}
  var newObj;

  if(typeof obj.clone === 'function')
    return obj.clone()

  // To copy something that is not an object, just return it:
  if(typeof obj !== 'object' && typeof obj !== 'function' || obj == null)
    return obj;

  if(typeof obj === 'object') {    
    // Copy the prototype:
    newObj = {}
    var proto = Object.getPrototypeOf(obj)
    Object.setPrototypeOf(newObj, proto)
  } else {
    // If the object is a function the function evaluate it:
    var aux
    newObj = eval('aux='+obj.toString())
    // And copy the prototype:
    newObj.prototype = obj.prototype
  }

  // Copy the object normal properties with a deep copy:
  for(var i in obj) {
    if(obj.hasOwnProperty(i)) {
      if(typeof obj[i] !== 'object')
        newObj[i] = obj[i]
      else
        newObj[i] = copy(obj[i])
    }
  }

  return newObj;
}

Com esta cópia não consigo encontrar nenhuma diferença entre o original e o copiado exceto se o original utilizou fechos na sua construção, por isso penso que é uma boa implementação.

Espero que ajude

VinGarcia
fonte
1

para array, pode-se usar

var arr = [1,2,3]; 
var arr_2 = arr ; 

print ( arr_2 ); 

arr = arr.slice (0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 
tlqtangok
fonte
1

Objetos e matrizes em JavaScript usam chamada por referência, se você atualizar o valor copiado, ele pode refletir no objeto original. Para evitar isso, você pode clonar profundamente o objeto, para evitar que a referência seja passada, usando o comando run do método cloneDeep da biblioteca lodash

npm instalar lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
vinay dagar
fonte