Como clonar corretamente um objeto JavaScript?

3078

Eu tenho um objeto x. Gostaria de copiá-lo como objeto y, de modo que as alterações ynão sejam modificadas x. Percebi que copiar objetos derivados de objetos JavaScript internos resultará em propriedades extras e indesejadas. Isso não é um problema, pois estou copiando um dos meus próprios objetos construídos literalmente.

Como clonar corretamente um objeto JavaScript?

soundly_typed
fonte
30
Veja esta pergunta: stackoverflow.com/questions/122102/…
Niyaz
256
Para JSON, eu usomObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh.
67
Eu realmente não entendo por que ninguém sugere Object.create(o), ele faz tudo o que o autor pede?
Froginvasion
44
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; Após fazer isso, y.deep.keytambém será 2, daí Object.create não pode ser usado para clonagem ...
Ruben Stolk
17
@ r3wt que não trabalho ... Por favor, poste somente após fazer teste básico da solução ..
akshay

Respostas:

1561

Fazer isso para qualquer objeto em JavaScript não será simples ou direto. Você terá o problema de escolher erroneamente atributos do protótipo do objeto que devem ser deixados no protótipo e não copiados para a nova instância. Se, por exemplo, você estiver adicionando um clonemétodo Object.prototype, como algumas respostas mostram, será necessário pular explicitamente esse atributo. Mas e se houver outros métodos adicionais adicionados Object.prototypeou outros protótipos intermediários que você não conhece? Nesse caso, você copiará atributos que não deve, portanto, é necessário detectar atributos imprevistos e não locais com o hasOwnPropertymétodo

Além dos atributos não enumeráveis, você encontrará um problema mais difícil ao tentar copiar objetos que possuem propriedades ocultas. Por exemplo, prototypeé uma propriedade oculta de uma função. Além disso, o protótipo de um objeto é referenciado com o atributo __proto__, que também está oculto e não será copiado por um loop for / in repetindo os atributos do objeto de origem. Eu acho que __proto__pode ser específico para o intérprete JavaScript do Firefox e pode ser algo diferente em outros navegadores, mas você entendeu. Nem tudo é enumerável. Você pode copiar um atributo oculto se souber o nome dele, mas não conheço nenhuma maneira de descobri-lo automaticamente.

Outro obstáculo na busca por uma solução elegante é o problema de configurar a herança do protótipo corretamente. Se o protótipo do objeto de origem for Object, simplesmente criar um novo objeto geral com {}funcionará, mas se o protótipo da origem for algum descendente Object, você terá os membros adicionais ausentes daquele protótipo que você pulou usando o hasOwnPropertyfiltro ou qual estavam no protótipo, mas não eram enumeráveis ​​em primeiro lugar. Uma solução pode ser chamar a constructorpropriedade do objeto de origem para obter o objeto de cópia inicial e depois copiar os atributos, mas você ainda não obterá atributos não enumeráveis. Por exemplo, um Dateobjeto armazena seus dados como um membro oculto:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

A sequência de datas para d1será 5 segundos atrás da de d2. Uma maneira de tornar um Dateigual ao outro é chamando o setTimemétodo, mas isso é específico da Dateclasse. Eu não acho que exista uma solução geral à prova de balas para esse problema, embora eu ficaria feliz em estar errado!

Quando eu tive que implementar profunda geral copiando acabei comprometer assumindo que eu só precisa copiar uma planície Object, Array, Date, String, Number, ou Boolean. Os últimos 3 tipos são imutáveis, então eu poderia fazer uma cópia superficial e não me preocupar com a alteração. Eu assumi ainda que qualquer elemento contido Objectou Arraytambém seria um dos 6 tipos simples nessa lista. Isso pode ser feito com código como o seguinte:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

A função acima funcionará adequadamente para os 6 tipos simples que eu mencionei, desde que os dados nos objetos e matrizes formem uma estrutura em árvore. Ou seja, não há mais de uma referência aos mesmos dados no objeto. Por exemplo:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

Ele não poderá manipular nenhum objeto JavaScript, mas pode ser suficiente para muitos propósitos, desde que você não assuma que ele funcionará apenas para qualquer coisa que você jogue nele.

A. Levy
fonte
5
quase funcionou bem em um nodejs - apenas tive que alterar a linha para (var i = 0, var len = comprimento obj.l; i <len; ++ i) {para for (var i = 0; i <comprimento obj.; ++ i) {
Trindaz 28/03

5
Para futuros googlers: mesma cópia detalhada, passando referências recursivamente em vez de usar as instruções 'return' em gist.github.com/2234277
Trindaz

8
Hoje em dia JSON.parse(JSON.stringify([some object]),[some revirer function])seria uma solução?
KooiInc

5
No primeiro trecho, você tem certeza de que não deveria ser var cpy = new obj.constructor()?
Cyon

8
Atribuir objeto não parecem fazer uma cópia fiel no Chrome, mantém referência ao objeto original - acabou usando JSON.stringify e JSON.parse para clone - funcionou perfeitamente
1owk3y

974

Se você não usar Dates, functions, undefined, regExp ou Infinity dentro do seu objeto, um liner muito simples é JSON.parse(JSON.stringify(object)):

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

Isso funciona para todos os tipos de objetos que contêm objetos, matrizes, strings, booleanos e números.

Consulte também este artigo sobre o algoritmo de clone estruturado de navegadores usado ao postar mensagens para e de um trabalhador. Ele também contém uma função para clonagem profunda.


42
Observe que isso só pode ser usado para teste. Em primeiro lugar, está longe de ser o ideal em termos de tempo e consumo de memória. Em segundo lugar, nem todos os navegadores possuem esses métodos.
Nux

2
Você sempre pode incluir JSON2.js ou JSON3.js. Você precisaria deles para o seu aplicativo de qualquer maneira. Mas concordo que essa pode não ser a melhor solução, pois o JSON.stringify não inclui propriedades herdadas.
Tim Hong

78
@ Nux, por que não otimizar em termos de tempo e memória? MiJyn diz: "A razão pela qual esse método é mais lento que a cópia superficial (em um objeto profundo) é que esse método, por definição, copia profundamente. Mas, como o JSON é implementado no código nativo (na maioria dos navegadores), isso é consideravelmente mais rápido. do que usar qualquer outra solução de cópia profunda baseada em javascript e às vezes pode ser mais rápido que uma técnica de cópia superficial baseada em javascript (consulte: jsperf.com/cloning-an-object/79). " stackoverflow.com/questions/122102/…
BeauCielBleu

16
Eu só quero adicionar uma atualização para isso em outubro de 2014. O Chrome 37+ é mais rápido com JSON.parse (JSON.stringify (oldObject)); A vantagem de usar isso é que é muito fácil para um mecanismo javascript ver e otimizar para algo melhor, se desejar.
9484 miraclek

17
Atualização de 2016: isso deve funcionar agora em praticamente todos os navegadores amplamente utilizados. (consulte Posso usar ... ) A questão principal agora seria se o desempenho é suficiente.
James Foster

783

Com o jQuery, você pode copiar superficialmente com estender :

var copiedObject = jQuery.extend({}, originalObject)

alterações subseqüentes no copiedObjectnão afetarão o originalObjecte vice-versa.

Ou para fazer uma cópia profunda :

var copiedObject = jQuery.extend(true, {}, originalObject)

164
ou até:var copiedObject = jQuery.extend({},originalObject);
Grant McLean

82
Também é útil para especificar verdadeiro como o primeiro parâmetro para a cópia profunda:jQuery.extend(true, {}, originalObject);
Will Shaver

6
Sim, achei este link útil (a mesma solução que Pascal) stackoverflow.com/questions/122102/…
Garry English

3
Apenas uma nota, este não copia o proto construtor do objeto original
Sam Jones

1
De acordo com stackoverflow.com/questions/184710/… , parece que "cópia superficial" copia apenas a referência de originalObject, então por que aqui diz isso ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa.... Desculpe por estar realmente confuso.
Carr

687

No ECMAScript 6, existe o método Object.assign , que copia valores de todas as propriedades próprias enumeráveis ​​de um objeto para outro. Por exemplo:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Mas esteja ciente de que objetos aninhados ainda são copiados como referência.


Sim, acredito que esse Object.assigné o caminho a percorrer. Também é fácil preenchê-lo também: gist.github.com/rafaelrinaldi/43813e707970bd2d77fa

22
Lembre-se também de que isso copiará os "métodos" definidos por meio de literais de objetos (uma vez que são enumeráveis), mas não os métodos desafiados pelo mecanismo de "classe" (uma vez que não são enumeráveis).
Marcus Junius Brutus

16
Eu acho que deve ser mencionado que isso não é suportado pelo IE, exceto o Edge. Algumas pessoas ainda usam isso.
Saulius 29/05

1
É o mesmo que @EugeneTiurin, sua resposta.
Wilt

5
Falando da experiência aqui ... NÃO use isso. Os objetos foram projetados para serem hierárquicos e você copiará apenas o primeiro nível, levando a atribuir o objeto inteiro copiado. Confie em mim, pode economizar dias de coçar a cabeça.
ow3n

232

Por MDN :

  • Se você deseja uma cópia superficial, use Object.assign({}, a)
  • Para cópia "profunda", use JSON.parse(JSON.stringify(a))

Não há necessidade de bibliotecas externas, mas é necessário verificar primeiro a compatibilidade do navegador .


8
O JSON.parse (JSON.stringify (a)) parece bonito, mas antes de usá-lo, recomendo fazer uma comparação do tempo necessário para a coleção desejada. Dependendo do tamanho do objeto, talvez essa não seja a opção mais rápida.
Edza

3
O método JSON que notei converte objetos de data em seqüências de caracteres, mas não volta a datas. É necessário lidar com a diversão dos fusos horários no Javascript e corrigir manualmente as datas. Pode haver casos semelhantes para outros tipos além de datas
Steve Seeger

para Date, eu usaria o moment.js, pois possui clonefuncionalidade. veja mais aqui momentjs.com/docs/#/parsing/moment-clone
Tareq

Isso é bom, mas tome cuidado se o objeto tem funções dentro
lesolorzanov

Outro problema com a análise de json são as referências circulares. Se houver alguma referência circular dentro do objeto, isso será interrompido
philoj 1/19/19

133

Existem muitas respostas, mas nenhuma menciona Object.create do ECMAScript 5, que reconhecidamente não fornece uma cópia exata, mas define a fonte como o protótipo do novo objeto.

Portanto, essa não é uma resposta exata para a pergunta, mas é uma solução de uma linha e, portanto, elegante. E funciona melhor em 2 casos:

  1. Onde tal herança é útil (duh!)
  2. Onde o objeto de origem não será modificado, tornando a relação entre os dois objetos um problema.

Exemplo:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

Por que considero esta solução superior? É nativo, portanto, sem loop, sem recursão. No entanto, navegadores mais antigos precisarão de um polyfill.


Nota: Object.create não é uma cópia profunda (o itpastorn não mencionou recursão). Prova:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts

103
Isso é herança prototípica, não clonagem. Essas são coisas completamente diferentes. O novo objeto não possui propriedades próprias, apenas aponta para as propriedades do protótipo. O objetivo da clonagem é criar um novo objeto novo que não faça referência a nenhuma propriedade em outro objeto.
d13

7
Eu concordo com você completamente. Concordo também que isso não é clonagem, como pode ser "pretendido". Mas vamos lá pessoal, adote a natureza do JavaScript em vez de tentar encontrar soluções obscuras que não são padronizadas. Claro, você não gosta de protótipos e todos eles são "blá" para você, mas na verdade são muito úteis se você souber o que está fazendo.
Froginvasion

4
@RobG: Este artigo explica a diferença entre referência e clonagem: en.wikipedia.org/wiki/Cloning_(programming) . Object.createaponta para as propriedades dos pais por meio de referências. Isso significa que, se os valores das propriedades dos pais mudarem, a criança também mudará. Isso tem alguns efeitos colaterais surpreendentes com matrizes e objetos aninhados que podem levar a erros difíceis de encontrar no seu código, se você não souber : jsbin.com/EKivInO/2 . Um objeto clonado é um objeto completamente novo e independente que possui as mesmas propriedades e valores que o pai, mas não está conectado ao pai.
d13

1
Isso engana as pessoas ... Object.create () pode ser usado como um meio de herança, mas a clonagem não está nem perto disso.
prajnavantha

128

Uma maneira elegante de clonar um objeto Javascript em uma linha de código

Um Object.assignmétodo faz parte do padrão ECMAScript 2015 (ES6) e faz exatamente o que você precisa.

var clone = Object.assign({}, obj);

O método Object.assign () é usado para copiar os valores de todas as propriedades enumeráveis ​​de um ou mais objetos de origem para um objeto de destino.

Consulte Mais informação...

O polyfill para suportar navegadores mais antigos:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

Desculpe a pergunta idiota, mas por que Object.assigndois parâmetros são necessários quando a valuefunção no polyfill usa apenas um parâmetro?
Qwertie

Ontem @Qwertie Todos os argumentos são iterados e fundidos em um objeto, priorizando as propriedades do último arg passado
Eugene Tiurin

Ah, entendi, obrigado (eu não estava familiarizado com o argumentsobjeto antes.) Estou tendo problemas para encontrar Object()via Google ... é uma perda de qualidade, não é?
Qwertie

44
isso só vai realizar uma rasa "clonagem"
Marcus Junius Brutus

1
Esta resposta é exatamente a mesma: stackoverflow.com/questions/122102/… Eu sei que é da mesma pessoa, mas você deve fazer referência em vez de apenas copiar a resposta.
lesolorzanov

87

Existem vários problemas com a maioria das soluções na internet. Então, decidi fazer um acompanhamento, que inclui por que a resposta aceita não deveria ser aceita.

situação inicial

Quero copiar em profundidade um Javascript Objectcom todos os seus filhos e seus filhos e assim por diante. Mas desde que eu não sou um tipo de desenvolvedor normal, a minha Objecttem normais properties , circular structurese mesmo nested objects.

Então, vamos criar um circular structuree um nested objectprimeiro.

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Vamos reunir tudo em um Objectnome a.

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

Em seguida, queremos copiar apara uma variável chamada be modificá-la.

var b = a;

b.x = 'b';
b.nested.y = 'b';

Você sabe o que aconteceu aqui, porque senão você nem chegaria a essa grande questão.

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

Agora vamos encontrar uma solução.

JSON

A primeira tentativa que tentei foi usar JSON.

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

Não perca muito tempo com isso, você receberá TypeError: Converting circular structure to JSON.

Cópia recursiva (a "resposta" aceita)

Vamos dar uma olhada na resposta aceita.

function cloneSO(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

Parece bom, hein? É uma cópia recursiva do objeto e também lida com outros tipos Date, mas isso não era um requisito.

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

Recursão e circular structuresnão funciona bem em conjunto ...RangeError: Maximum call stack size exceeded

solução nativa

Depois de discutir com meu colega de trabalho, meu chefe nos perguntou o que havia acontecido e ele encontrou uma solução simples depois de pesquisar no Google. É chamado Object.create.

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

Esta solução foi adicionada ao Javascript há algum tempo e até lida com circular structure.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

... e veja bem, não funcionou com a estrutura aninhada dentro.

polyfill para a solução nativa

Existe um polyfill Object.createno navegador mais antigo, assim como o IE 8. É algo como recomendado pelo Mozilla e, é claro, não é perfeito e resulta no mesmo problema da solução nativa .

function F() {};
function clonePF(o) {
    F.prototype = o;
    return new F();
}

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

Eu coloquei Ffora do escopo para que possamos dar uma olhada no que instanceofnos diz.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

Mesmo problema que a solução nativa , mas saída um pouco pior.

a melhor solução (mas não perfeita)

Ao pesquisar, encontrei uma pergunta semelhante ( em Javascript, ao executar uma cópia em profundidade, como evito um ciclo, devido a uma propriedade ser "isto"? ), Mas com uma solução muito melhor.

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

E vamos dar uma olhada na saída ...

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

Os requisitos são atendidos, mas ainda existem alguns problemas menores, incluindo a alteração instancede nestede circpara Object.

A estrutura das árvores que compartilham uma folha não será copiada, elas se tornarão duas folhas independentes:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

conclusão

A última solução usando recursão e um cache, pode não ser o melhor, mas é um verdadeiro deep-cópia do objeto. Ele lida com simples properties, circular structurese nested object, mas vai atrapalhar a instância deles durante a clonagem.

jsfiddle


12
de modo que o conlcusion é evitar esse problema :)
mikus

@mikus até que exista uma especificação real que cubra mais do que apenas os casos de uso básicos, sim.
Fabio Poloni

2
Uma análise correta das soluções fornecidas acima, mas a conclusão tirada pelo autor indica que não há solução para essa questão.
Amir Mog

2
É uma pena que o JS não inclua a função clone nativa.
L00k

1
Entre todas as respostas principais, acho que isso está próximo da correta.
KTU

77

Se você concorda com uma cópia superficial, a biblioteca underscore.js possui um método clone .

y = _.clone(x);

ou você pode estendê-lo como

copiedObject = _.extend({},originalObject);

2
Obrigado. Usando esta técnica em um servidor Meteor.
Turbo

Para começar rapidamente com o lodash, recomendo aprender o npm, o Browserify e o lodash. Eu consegui o clone para trabalhar com 'npm i --save lodash.clone' e depois 'var clone = require (' lodash.clone ');' Para conseguir o trabalho, você precisa de algo como o browserify. Depois de instalá-lo e aprender como ele funciona, você usará 'browserify yourfile.js> bundle.js; start chrome index.html' toda vez que executar seu código (em vez de acessar o Chrome diretamente). Isso reúne seu arquivo e todos os arquivos necessários do módulo npm em bundle.js. Você provavelmente pode economizar tempo e automatizar esta etapa com o Gulp.
Aaron Sino

65

OK, imagine que você tem este objeto abaixo e deseja cloná-lo:

let obj = {a:1, b:2, c:3}; //ES6

ou

var obj = {a:1, b:2, c:3}; //ES5

a resposta depende principalmente de qual ECMAscript você está usando, ES6+pode simplesmente usar Object.assigno clone:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

ou usando o operador spread como este:

let cloned = {...obj}; //new {a:1, b:2, c:3};

Mas se você estiver usando ES5, poderá usar alguns métodos, mas o JSON.stringify, apenas certifique-se de não usar uma grande quantidade de dados para copiar, mas em muitos casos pode ser uma maneira útil de uma linha:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

Você pode dar um exemplo do que big chunk of dataseria igual a? 100kb? 100MB? Obrigado!
user1063287

Sim, @ user1063287, que basicamente os dados maiores, o desempenho pior ... então isso realmente depende, não um kb, mb ou gb, é mais sobre quantas vezes você quer fazer isso também ... Também não vai funcionar para funções e outros animais ...
Alireza

3
Object.assignfaz uma cópia superficial (assim como a propagação, @Alizera)
Bogdan D

Você não pode usar let in es5: ^) @Alireza
SensationSama

40

Uma solução particularmente deselegante é usar a codificação JSON para fazer cópias profundas de objetos que não possuem métodos de membro. A metodologia é codificar JSON seu objeto de destino e, ao decodificá-lo, você obtém a cópia que está procurando. Você pode decodificar quantas vezes quiser para fazer quantas cópias precisar.

Obviamente, as funções não pertencem ao JSON, portanto, isso funciona apenas para objetos sem métodos de membro.

Essa metodologia foi perfeita para o meu caso de uso, pois estou armazenando blobs JSON em um armazenamento de valor-chave e, quando eles são expostos como objetos em uma API JavaScript, cada objeto na verdade contém uma cópia do estado original do objeto, para que possamos pode calcular o delta após o chamador ter alterado o objeto exposto.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

Por que as funções não pertencem ao JSON? Já os vi transferidos como JSON mais de uma vez ...
the_drow

5
As funções não fazem parte da especificação JSON, pois não são uma maneira segura (ou inteligente) de transferir dados, e foi para isso que o JSON foi criado. Eu sei que o codificador JSON nativo no Firefox simplesmente ignora as funções passadas para ele, mas não tenho certeza sobre o comportamento de outras pessoas.
Kris Walker

1
@mark: { 'foo': function() { return 1; } }é um objeto construído literalmente.
abarnert

As funções @abarnert não são dados. "Literais de função" é um nome impróprio - já que as funções podem conter código arbitrário, incluindo atribuições e todo tipo de coisa "não serializável".
vemv

35

Você pode simplesmente usar uma propriedade de spread para copiar um objeto sem referências. Mas tenha cuidado (consulte os comentários), a 'cópia' está apenas no nível mais baixo de objeto / matriz. Propriedades aninhadas ainda são referências!


Clone completo:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

Clone com referências no segundo nível:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

Na verdade, o JavaScript não suporta nativamente clones profundos. Use uma função utilitária. Por exemplo Ramda:

http://ramdajs.com/docs/#clone


1
Isso não funciona ... ele iria trabalhar provavelmente quando x vai ser um array, por exemplo x = [ 'ab', 'cd', ...]
Kamil Kiełczewski

3
Isso funciona, mas lembre-se de que é uma cópia SHALLOW, portanto, quaisquer referências profundas a outros objetos permanecem referências!
Bugs Bunny

Um clone parcial também pode acontecer desta maneira:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Cristian Traina

25

Para aqueles que usam AngularJS, também há um método direto para clonar ou estender os objetos nesta biblioteca.

var destination = angular.copy(source);

ou

angular.copy(source, destination);

Mais na documentação do angular.copy ...


2
Esta é uma cópia aprofundada da EFY.
Zamnuts

22

Aqui está uma função que você pode usar.

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;
}

10
Essa resposta está bem próxima, mas não totalmente correta. Se você tentar clonar um objeto Date, não obterá a mesma data porque a chamada para a função do construtor Date inicializa a nova Data com a data / hora atual. Esse valor não é enumerável e não será copiado pelo loop for / in.
A. Levy

Não é perfeito, mas agradável para os casos básicos. Por exemplo, permitindo a clonagem simples de um argumento que pode ser um Objeto, Matriz ou String básico.
James_womack #

Voto positivo para chamar corretamente o construtor usando new. A resposta aceita não.
GetFree

funciona no nó tudo o resto! ainda deixou links de referência
user956584

O pensamento recursivo é ótimo. Mas se o valor for um array, ele funcionará?
V10

22

A. A resposta de Lucy está quase completa, aqui está minha pequena contribuição: existe uma maneira de lidar com referências recursivas , veja esta linha

if(this[attr]==this) copy[attr] = copy;

Se o objeto for XML elemento DOM, devemos usar cloneNode vez

if(this.cloneNode) return this.cloneNode(true);

Inspirado no estudo exaustivo de A.Levy e na abordagem de prototipagem de Calvin, ofereço esta solução:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

Veja também a nota de Andy Burke nas respostas.


3
Date.prototype.clone = function() {return new Date(+this)};
RobG

22

Neste artigo: Como copiar matrizes e objetos em Javascript por Brian Huisman:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

4
Isso está próximo, mas não funciona para nenhum objeto. Tente clonar um objeto Date com isso. Nem todas as propriedades são enumeráveis; portanto, nem todas serão exibidas no loop for / in.
A. Levy

Adicionar ao protótipo de objeto como esse quebrou o jQuery para mim. Mesmo quando mudei o nome para clone2.
iPadDeveloper2011

3
@ iPadDeveloper2011 O código acima continha um erro, ao criar uma variável global chamada 'i' '(para i neste)', em vez de '(para var i neste)'. Eu tenho karma suficiente para editar e corrigi-lo, então fiz.
Mikemaccana 22/09/12

1
Calvin @: isso deve ser criado como uma propriedade não enumerável, caso contrário 'clone' aparecerá em loops 'for'.
Mikemaccana 1/10/12

2
por que não é var copiedObj = Object.create(obj);uma ótima maneira também?
Dan P.

19

No ES-6, você pode simplesmente usar Object.assign (...). Ex:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

Uma boa referência está aqui: https://googlechrome.github.io/samples/object-assign-es6/


12
Não clona profundamente o objeto.
agosto

Isso é uma tarefa, não uma cópia. clone.Title = "apenas um clone" significa que obj.Title = "apenas um clone".
HoldOffHunger

@HoldOffHunger Você está enganado. Verifique no console JS do seu navegador ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
collapsar

@ collapsar: Foi exatamente isso que eu verifiquei, então console.log (pessoa) será "Whazzup", não "Thor Odinson". Veja o comentário de agosto.
HoldOffHunger

1
@HoldOffHunger Não acontece no Chrome 60.0.3112.113 nem no Edge 14.14393; O comentário de agosto não se aplica, pois os valores dos tipos primitivos de objpropriedades de fato são clonados. Os valores da propriedade que são os próprios objetos não serão clonados.
Collapsar

18

No ECMAScript 2018

let objClone = { ...obj };

Esteja ciente de que objetos aninhados ainda são copiados como referência.


1
Obrigado pela dica de que objetos aninhados ainda são copiados como referência! Eu quase enlouqueci ao depurar meu código porque modifiquei as propriedades aninhadas no "clone", mas o original foi modificado.
Benny Neugebauer

Este é o ES2016, não 2018, e essa resposta foi dada dois anos antes .
Dan Dascalescu 13/06/19

Então, o que se eu quiser cópia da propriedade aninhada bem
Sunil Garg

1
@SunilGarg Para copiar propriedade aninhada assim você pode usar const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

16

Usando o Lodash:

var y = _.clone(x, true);

5
OMG, seria insano reinventar a clonagem. Esta é a única resposta sã.
Dan Ross

5
Eu prefiro, _.cloneDeep(x)pois é essencialmente a mesma coisa que acima, mas lê melhor.
garbanzio


13

Você pode clonar um objeto e remover qualquer referência da anterior usando uma única linha de código. Simplesmente faça:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

Para navegadores / mecanismos que atualmente não oferecem suporte ao Object.create, você pode usar este polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

1
+1 Object.create(...)parece definitivamente o caminho a percorrer.
René Nyffenegger 30/06

Resposta perfeita. Talvez você possa adicionar uma explicação para Object.hasOwnProperty? Dessa forma, as pessoas sabem como impedir a pesquisa no link do protótipo.
Froginvasion 9/08/14

Funciona bem, mas em quais navegadores o polyfill funciona?
Ian Lunn

11
Isso está criando obj2 com um obj1, pois é um protótipo. Funciona apenas porque você está sombreando o textmembro no obj2. Você não está fazendo uma cópia, apenas adiando para a cadeia de protótipos quando um membro não for encontrado no obj2.
Nick Desaulniers

2
Isso NÃO o cria "sem referências", apenas move a referência para o protótipo. Ainda é uma referência. Se uma propriedade mudar no original, a propriedade prototype no "clone" também mudará. Não é um clone.
Jimbo Jonny

13

Nova resposta para uma pergunta antiga! Se você tem o prazer de usar o ECMAScript 2016 (ES6) com a Spread Syntax , é fácil.

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

Isso fornece um método limpo para uma cópia superficial de um objeto. Fazer uma cópia profunda, significando criar uma nova cópia de todos os valores em todos os objetos aninhados recursivamente, requer uma das soluções mais pesadas acima.

JavaScript continua evoluindo.


2
ele não funciona quando você tem funções definidas em objetos
Petr Marek

pelo que vejo o operador spread só funciona com iterables - developer.mozilla.org diz: # var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Oleh

@Oleh use `{... obj} em vez de [... obj];`
manikant gautam
@manikantgautam Eu estava usando Object.assign () antes, mas agora a sintaxe de propagação de objetos é suportada no Chrome, Firefox (ainda não no Edge e Safari). Sua proposta ECMAScript ... mas Babel suporta até onde eu posso ver, então provavelmente é seguro de usar.
Oleh
12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Solução ES6 se você deseja (superficial) clonar uma instância de classe e não apenas um objeto de propriedade.

flori
fonte
Como isso é diferente let cloned = Object.assign({}, obj)?
Ceztko 21/05/19
10

Eu acho que há uma resposta simples e funcional. Na cópia profunda, existem duas preocupações:

  1. Mantenha as propriedades independentes entre si.
  2. E mantenha os métodos vivos no objeto clonado.

Então, acho que uma solução simples será primeiro serializar e desserializar e, em seguida, fazer uma atribuição para copiar funções também.

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

Embora esta pergunta tenha muitas respostas, espero que esta também ajude.

ConducedClever
fonte
Embora se eu tiver permissão para importar o lodash, prefiro usá-lo cloneDeep.
ConductedClever
2
Estou usando JSON.parse (JSON.stringify (origem)). Sempre trabalhando.
Misha
2
@Misha, assim você perderá as funções. O termo 'obras' tem muitos significados.
ConductedClever
E lembre-se de que, da maneira que mencionei, apenas as funções da primeira camada serão copiadas. Portanto, se tivermos alguns objetos um dentro do outro, a única maneira é copiar campo por campo recursivamente.
ConductedClever
9

Para uma cópia profunda e clone, JSON.stringify e JSON.parse o objeto:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
Nishant Dwivedi
fonte
bastante inteligente ... alguma desvantagem dessa abordagem?
Aleks
8

Esta é uma adaptação do código de A. Levy para também manipular a clonagem de funções e referências múltiplas / cíclicas - o que isso significa é que, se duas propriedades na árvore clonada forem referências do mesmo objeto, a árvore de objetos clonados terá essas As propriedades apontam para um e o mesmo clone do objeto referenciado. Isso também resolve o caso de dependências cíclicas que, se não tratadas, levam a um loop infinito. A complexidade do algoritmo é O (n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

        // Handle Date
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


        throw new Error("Unable to copy obj! Its type isn't supported.");
    }
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

Alguns testes rápidos

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
Radu Simionescu
fonte
1
Em setembro de 2016, essa é a única solução correta para a questão.
DomQ
6

Eu só queria adicionar a todos Object.create soluções neste post que isso não funciona da maneira desejada com o nodejs.

No Firefox, o resultado de

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

é

{test:"test"}.

Em nodejs é

{}
heinob
fonte
Isso é herança prototípica, não clonagem.
d13
1
@ d13 enquanto o seu argumento for válido, observe que não existe uma maneira padronizada no JavaScript de clonar um objeto. Essa é uma herança prototípica, mas pode ser usada como clone, se você entender os conceitos.
Froginvasion 8/08
@froginvasion. O único problema com o uso de Object.create é que objetos e matrizes aninhados são apenas referências de ponteiro aos objetos e matrizes aninhados do protótipo. jsbin.com/EKivInO/2/edit?js,console . Tecnicamente, um objeto "clonado" deve ter suas próprias propriedades exclusivas que não são referências compartilhadas a propriedades em outros objetos.
d13
@ d13 ok, entendo seu ponto agora. Mas o que eu quis dizer é que muitas pessoas estão alienadas com o conceito de herança prototípica e, para mim, não aprendem como isso funciona. Se não me engano, seu exemplo pode ser corrigido apenas ligando Object.hasOwnPropertypara verificar se você é o proprietário ou não da matriz. Sim, isso adiciona complexidade adicional para lidar com a herança prototípica.
Froginvasion
6
function clone(src, deep) {

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object"){
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    }

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]"){
        return src.clone(deep);
    }

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
        return src.cloneNode(deep);
    }

    //Date
    if(toString.call(src) == "[object Date]"){
        return new Date(src.getTime());
    }

    //RegExp
    if(toString.call(src) == "[object RegExp]"){
        return new RegExp(src);
    }

    //Function
    if(toString.call(src) == "[object Function]"){
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });

    }

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]"){
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep){
            index = ret.length;
            while(index--){
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }

    return ret;
};
user1547016
fonte
2
if(!src && typeof src != "object"){. Eu acho que ||não deveria &&.
MikeM
6

Como o mindeavor afirmou que o objeto a ser clonado é um objeto 'literalmente construído', uma solução pode ser simplesmente gerar o objeto várias vezes, em vez de clonar uma instância do objeto:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
Bert Regelink
fonte
6

Eu escrevi minha própria implementação. Não tenho certeza se isso conta como uma solução melhor:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

A seguir está a implementação:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}
yazjisuhail
fonte
não está funcionando para o meu objeto, embora meu caso seja um pouco complexo.
Sajuuk 02/01/19