Qual é a maneira mais eficiente de clonar profundamente um objeto em JavaScript?

5180

Qual é a maneira mais eficiente de clonar um objeto JavaScript? Vi obj = eval(uneval(o));ser usado, mas isso não é padrão e é suportado apenas pelo Firefox .

Eu fiz coisas como, obj = JSON.parse(JSON.stringify(o));mas questiono a eficiência.

Também vi funções de cópia recursiva com várias falhas.
Estou surpreso que não exista solução canônica.

jschrab
fonte
566
Eval não é mau. Usar mal o eval é. Se você tem medo de seus efeitos colaterais, está usando errado. Os efeitos colaterais que você teme são as razões para usá-lo. A propósito, alguém respondeu à sua pergunta?
James
15
A clonagem de objetos é um negócio complicado, especialmente com objetos personalizados de coleções arbitrárias. Provavelmente por isso que não existe uma maneira imediata de fazer isso.
b01
12
eval()geralmente é uma má ideia, porque muitos otimizadores de mecanismo Javascript precisam ser desativados ao lidar com variáveis ​​definidas viaeval . Apenas ter eval()seu código pode levar a um desempenho pior.
precisa saber é o seguinte
2
Possível duplicado da maioria maneira elegante para clonar um objeto JavaScript
John Slegers
12
Observe que o JSONmétodo perderá todos os tipos de Javascript que não têm equivalente em JSON. Por exemplo: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))irá gerar{a: null, b: null, c: null, g: false}
oriadam

Respostas:

4731

Clonagem profunda nativa

É chamado de "clonagem estruturada", funciona experimentalmente no nó 11 e posterior e, esperançosamente, chegará aos navegadores. Veja esta resposta para mais detalhes.

Clonagem rápida com perda de dados - JSON.parse / stringify

Se você não usar Dates, funções undefined, Infinity, regexps, Mapas, Conjuntos, gotas, filelists, ImageDatas, Arrays esparsos, datilografado matrizes ou outros tipos complexos dentro de seu objeto, uma forma muito simples um forro de clone profundo um objeto é:

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'
  re: /.*/,  // lost
}
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()

Veja a resposta de Corban para referências.

Clonagem confiável usando uma biblioteca

Como a clonagem de objetos não é trivial (tipos complexos, referências circulares, função etc.), a maioria das principais bibliotecas fornece função para clonar objetos. Não reinvente a roda - se você já estiver usando uma biblioteca, verifique se ela possui uma função de clonagem de objetos. Por exemplo,

ES6

Para completar, observe que o ES6 oferece dois mecanismos de cópia rasos: Object.assign()e a sintaxe de propagação . que copia valores de todas as propriedades próprias enumeráveis ​​de um objeto para outro. Por exemplo:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax
Dan Dascalescu
fonte
7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js na linha 276 (há um pouco de código que faz outra coisa, mas o código para "como fazer isso em JS" está lá :))
Rune FS
7
Aqui está o código JS atrás da cópia profunda jQuery, para qualquer pessoa interessada: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W
194
Woah! Só para ficar bem claro: não faço ideia do porquê dessa resposta ter sido escolhida como a resposta certa, foi uma resposta às respostas abaixo: stackoverflow.com/a/122190/6524 (que estava recomendando .clone(), que não é o código certo a ser usando neste contexto). Infelizmente, essa questão passou por tantas revisões que a discussão original não é mais aparente! Por favor, siga os conselhos de Corban e escreva um loop ou copie as propriedades diretamente para um novo objeto, se você se preocupa com a velocidade. Ou teste você mesmo!
precisa saber é o seguinte
9
Esta é uma pergunta sobre JavaScript (sem menção ao jQuery).
Gphilip
60
Como alguém faria isso sem usar o jQuery?
precisa
2266

Faça o checkout deste benchmark: http://jsben.ch/#/bWfk9

Nos meus testes anteriores, onde a velocidade era a principal preocupação que encontrei

JSON.parse(JSON.stringify(obj))

para ser a maneira mais lenta de clonar profundamente um objeto (é mais lento que o jQuery.extend com o deepsinalizador definido como verdadeiro de 10 a 20%).

O jQuery.extend é muito rápido quando o deepsinalizador é definido como false(clone superficial). É uma boa opção, pois inclui alguma lógica extra para validação de tipo e não copia propriedades indefinidas, etc., mas isso também o torna um pouco mais lento.

Se você conhece a estrutura dos objetos que está tentando clonar ou pode evitar matrizes aninhadas profundas, pode escrever um for (var i in obj)loop simples para clonar seu objeto enquanto verifica hasOwnProperty, e será muito mais rápido que o jQuery.

Por fim, se você estiver tentando clonar uma estrutura de objeto conhecida em um hot loop, poderá obter MUITO MAIS DESEMPENHO simplesmente alinhando o procedimento de clonagem e construindo manualmente o objeto.

Os mecanismos de rastreamento JavaScript são for..inruins para otimizar loops e a verificação de hasOwnProperty também o tornará lento. Clone manual quando a velocidade é uma necessidade absoluta.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Cuidado ao usar o JSON.parse(JSON.stringify(obj))método em Dateobjetos - JSON.stringify(new Date())retorna uma representação de string da data no formato ISO, que JSON.parse() não é convertida novamente em um Dateobjeto. Veja esta resposta para mais detalhes .

Além disso, observe que, pelo menos no Chrome 65, a clonagem nativa não é o caminho a percorrer. De acordo com o JSPerf, executar a clonagem nativa criando uma nova função é quase 800x mais lenta que o uso do JSON.stringify, que é incrivelmente rápido em todo o caminho.

Atualização para ES6

Se você estiver usando o Javascript ES6, tente este método nativo para clonagem ou cópia superficial.

Object.assign({}, obj);
Corban Brook
fonte
4
@trysis Object.create não é clonar o objeto, está usando o objeto de protótipo ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser
105
Este método também removerá o keysseu object, que tem functionscomo seus valores, porque o JSONnão suporta funções.
Karlen Kishmiryan
39
Lembre-se também de que o uso JSON.parse(JSON.stringify(obj))de objetos de data também converterá a data em UTC na representação de sequência no formato ISO8601 .
Dnlgmzddr 30/07/2015
31
A abordagem JSON também engasga com referências circulares.
rica Remer
28
@velop, Object.assign ({}, objToClone) parece que faz um clone superficial - usando-o enquanto brinca no console de ferramentas de desenvolvimento, o clone de objeto ainda aponta para uma referência ao objeto clonado. Então, eu não acho que seja realmente aplicável aqui.
Garrett Simpson
473

Supondo que você tenha apenas variáveis ​​e não funções no seu objeto, você pode apenas usar:

var newObject = JSON.parse(JSON.stringify(oldObject));
Sultan Shakir
fonte
86
o con desta abordagem como eu acabei de encontrar é se o seu objeto tem quaisquer funções (mina tem getters internos e setters), então estes são perdidos quando Stringified .. Se isso é tudo que você precisa este método é bem ..
Markive
31
@ Jason, 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. Porém, como JSONé implementado no código nativo (na maioria dos navegadores), isso será consideravelmente mais rápido do que 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 ).
MiJyn
35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer
32
Essa técnica destruirá também todos os Dateobjetos armazenados dentro do objeto, convertendo-os em forma de string.
Fstab
13
Ele vai deixar de copiar qualquer coisa que não faz parte do JSON especificação ( json.org )
cdmckay
397

Clonagem Estruturada

O padrão HTML inclui um algoritmo interno de clonagem / serialização estruturado que pode criar clones profundos de objetos. Ele ainda é limitado a certos tipos internos, mas, além dos poucos tipos suportados pelo JSON, também suporta Datas, RegExps, Mapas, Conjuntos, Blobs, FileLists, ImageDatas, Matrizes esparsas, Matrizes digitadas e provavelmente mais no futuro . Ele também preserva referências nos dados clonados, permitindo suportar estruturas cíclicas e recursivas que causariam erros no JSON.

Suporte no Node.js: experimental 🙂

O v8módulo no Node.js atualmente (a partir do Nó 11) expõe diretamente a API de serialização estruturada , mas essa funcionalidade ainda está marcada como "experimental" e está sujeita a alterações ou remoção em versões futuras. Se você estiver usando uma versão compatível, a clonagem de um objeto é tão simples quanto:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Suporte direto em navegadores: talvez eventualmente? 😐

Atualmente, os navegadores não fornecem uma interface direta para o algoritmo de clonagem estruturada, mas uma structuredClone()função global foi discutida no whatwg / html # 793 no GitHub . Conforme proposto atualmente, usá-lo para a maioria dos propósitos seria tão simples quanto:

const clone = structuredClone(original);

A menos que isso seja enviado, as implementações de clones estruturados dos navegadores são expostas apenas indiretamente.

Solução alternativa assíncrona: utilizável. 😕

A maneira mais baixa de criar um clone estruturado com APIs existentes é postar os dados através de uma porta de um MessageChannels . A outra porta emitirá um messageevento com um clone estruturado do anexo .data. Infelizmente, a escuta desses eventos é necessariamente assíncrona, e as alternativas síncronas são menos práticas.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Exemplo de uso:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Soluções síncronas: péssimas! 🤢

Não há boas opções para criar clones estruturados de forma síncrona. Aqui estão alguns truques impraticáveis.

history.pushState()e history.replaceState()ambos criam um clone estruturado de seu primeiro argumento e atribuem esse valor a history.state. Você pode usar isso para criar um clone estruturado de qualquer objeto como este:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Exemplo de uso:

Embora síncrono, isso pode ser extremamente lento. Incorre em toda a sobrecarga associada à manipulação do histórico do navegador. Se você chamar esse método repetidamente, o Chrome ficará temporariamente sem resposta.

O Notificationconstrutor cria um clone estruturado de seus dados associados. Ele também tenta exibir uma notificação do navegador para o usuário, mas isso falhará silenciosamente, a menos que você tenha solicitado permissão de notificação. Caso você tenha permissão para outros fins, fecharemos imediatamente a notificação que criamos.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Exemplo de uso:

Jeremy Banks
fonte
3
@rynah Acabei de analisar as especificações novamente e você está certo: os métodos history.pushState()e history.replaceState()definem de forma síncrona history.stateum clone estruturado do primeiro argumento. Um pouco estranho, mas funciona. Estou atualizando minha resposta agora.
Jeremy Banks
40
Isso é tão errado! Essa API não deve ser usada dessa maneira.
Fardin K.
209
Como o cara que implementou o pushState no Firefox, sinto uma estranha mistura de orgulho e repulsa por esse truque. Muito bem, galera.
Justin L.
não pushState ou corte Notificação não funcionar para alguns tipos de objetos como função
Shishir Arora
323

Se não houver nenhum, você pode tentar:

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

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}
ConroyP
fonte
20
A solução JQuery funcionará para elementos DOM, mas não apenas para qualquer objeto. Mootools tem o mesmo limite. Gostaria que eles tivessem um "clone" genérico para qualquer objeto ... A solução recursiva deve funcionar para qualquer coisa. Provavelmente é o caminho a percorrer.
jschrab
5
Essa função é interrompida se o objeto que está sendo clonado tiver um construtor que requer parâmetros. Parece que podemos alterá-lo para "var temp = new Object ()" e fazê-lo funcionar em todos os casos, não?
22413 Andrew Arnott
3
Andrew, se você mudar para var temp = new Object (), seu clone não terá o mesmo protótipo que o objeto original. Tente usar: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder
1
Semelhante a resposta de limscoder, ver minha resposta abaixo sobre como fazer isso sem chamar o construtor: stackoverflow.com/a/13333781/560114
Matt Browne
3
Para objetos que contêm referências a sub-partes (isto é, redes de objetos), isso não funciona: se duas referências apontam para o mesmo subobjeto, a cópia contém duas cópias diferentes. E se houver referências recursivas, a função nunca será encerrada (bem, pelo menos não da maneira que você deseja :-) Para esses casos gerais, você deve adicionar um dicionário de objetos já copiados e verificar se você já o copiou. ... A programação é complexa quando você usa uma linguagem simples
virtualnobi 11/11/13
153

A maneira eficiente de clonar (não clonar profundamente) um objeto 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;
    }
  });
}
Eugene Tiurin
fonte
82
Isso não é copiado recursivamente e, portanto, não oferece realmente uma solução para o problema de clonar um objeto.
mwhite
5
Esse método funcionou, embora eu tenha testado alguns e _.extend ({}, (obj)) foi MUITO MAIS RÁPIDO: 20x mais rápido que JSON.parse e 60% mais rápido que Object.assign, por exemplo. Ele copia todos os subobjetos muito bem.
Nico
11
@mwhite existe uma diferença entre clone e deep-clone. Na verdade, essa resposta é clonada, mas não é clonada em profundidade.
Meirion Hughes
57
o op pediu clone profundo. isso não faz clone profundo.
user566245
9
Este método faz uma cópia SHALLOW , e não uma cópia PROFUNDA ! Por isso, é uma resposta completamente errada !
Bharata
97

Código:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Teste:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);
Kamarey
fonte
3
que tal var obj = {}eobj.a = obj
neaumusic 5/05
5
Eu não entendo essa função. Suponha que from.constructorseja Datepor exemplo. Como o terceiro ifteste seria alcançado quando o segundo ifteste fosse bem-sucedido e faria com que a função retornasse (desde Date != Object && Date != Array)?
Adam McKee
1
@AdamMcKee Porque a passagem de argumentos javascript e a atribuição de variáveis ​​são complicadas . Essa abordagem funciona muito bem, incluindo datas (que de fato são tratadas pelo segundo teste) - aqui para testar aqui: jsfiddle.net/zqv9q9c6 .
brichins
1
@ NickSweeting: Tente - pode ser que funcione. Caso contrário - corrija-o e atualize a resposta. É assim que funciona aqui na comunidade :)
Kamarey
1
Essa função não clona o regex no teste, a condição "from.constructor! = Object && from.constructor! = Array" sempre retorna true para outros construtores como Number, Date e assim por diante.
aMarCruz
95

Isto é o que estou usando:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}
Alan
fonte
8
Isso não parece certo. cloneObject({ name: null })=>{"name":{}}
Niyaz 27/02
13
Isto é devido a outra coisa idiota em javascript typeof null > "object", mas Object.keys(null) > TypeError: Requested keys of a value that is not an object.mudar a condição deif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us
Isso atribuirá propriedades enumeráveis ​​herdadas de obj diretamente ao clone e assume que obj é um objeto simples.
585 RobGold
Isso também atrapalha as matrizes, que são convertidas em objetos com teclas numéricas.
blade
Não é um problema se você não usar nulo.
Jorge Bucaran
78

Cópia profunda por desempenho: classificada da melhor à pior

  • Reatribuição "=" (matrizes de cadeia, matrizes de número - apenas)
  • Fatia (matrizes de cadeia, matrizes de número - apenas)
  • Concatenação (matrizes de cadeia, matrizes de número - apenas)
  • Função personalizada: cópia for-loop ou recursiva
  • $ .extend do jQuery
  • JSON.parse (matrizes de cadeia, matrizes de número, matrizes de objeto - apenas)
  • Underscore.js _.clone 's (matrizes de cadeia, matrizes numéricas - apenas)
  • _.CloneDeep de Lo-Dash

Copie em profundidade uma matriz de seqüências de caracteres ou números (um nível - sem ponteiros de referência):

Quando uma matriz contém números e seqüências de caracteres - funções como .slice (), .concat (), .splice (), o operador de atribuição "=" e a função de clone do Underscore.js; fará uma cópia profunda dos elementos da matriz.

Onde a reatribuição tem o desempenho mais rápido:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

E .slice () tem melhor desempenho que .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Cópia profunda de uma matriz de objetos (dois ou mais níveis - ponteiros de referência):

var arr1 = [{object:'a'}, {object:'b'}];

Escreva uma função personalizada (com desempenho mais rápido que $ .extend () ou JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Use funções utilitárias de terceiros:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Onde $ .extend do jQuery tem melhor desempenho:

tfmontague
fonte
Testei alguns e _.extend ({}, (obj)) foi MUITO MAIS RÁPIDO: 20x mais rápido que JSON.parse e 60% mais rápido que Object.assign, por exemplo. Ele copia todos os subobjetos muito bem.
Nico
4
Todos os seus exemplos são superficiais, em um nível. Esta não é uma boa resposta. A questão era sobre clonagem profunda, ou seja, pelo menos dois níveis.
21817 Karl Morrison
1
Uma cópia profunda é quando um objeto é copiado na sua totalidade sem o uso de ponteiros de referência para outros objetos. As técnicas na seção "Cópia profunda de uma matriz de objetos", como jQuery.extend () e a função personalizada (que é recursiva), copiam objetos com "pelo menos dois níveis". Portanto, nem todos os exemplos são cópias de "um nível".
tfmontague
1
Eu gosto da sua função de cópia personalizada, mas você deve excluir valores nulos, caso contrário, todos os valores nulos serão convertidos em objetos, ou seja:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi
2
@HossamMourad - O bug foi corrigido por Josi em 1 de fevereiro (no comentário acima) e eu não atualizei corretamente a resposta. Lamentamos que esse bug tenha resultado em uma refatoração da sua base de código.
tfmontague
64
var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
Zibri
fonte
Boa resposta, mas isso falha em referências circulares.
Lucas
59

Objetos de cópia profunda em JavaScript (acho o melhor e o mais simples)

1. Usando JSON.parse (JSON.stringify (objeto));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.Usando o método criado

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Usando o link _.cloneDeep do Lo-Dash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Usando o método Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

MAS ERRADO QUANDO

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Utilizando o link Underscore.js _.clone Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

MAS ERRADO QUANDO

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH Playground de Benchmarking de Desempenho 1 ~ 3 http://jsben.ch/KVQLd Objetos de cópia profunda em JavaScript

Tính Ngô Quang
fonte
5
Object.assign()não executar uma cópia profunda
Roymunson
1
você deve adicionar referências para esses; isso seria muito útil
jcollum
quando usei o "método criado" em um objeto que contém uma matriz, não consegui usar pop () ou splice () nele, não entendo por quê? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();-lo jogar: TypeError: tmp.title.pop is not a function(claro pop () multa funciona se eu só do let tmp = data, mas então eu não posso modificar tmp sem afetar os dados)
hugogogo
Ei, seu último exemplo está errado. Na minha opinião, você deve usar _clone e não _cloneDeep para o exemplo errado.
Kenanyildiz 10/12/19
Este método criado (2.) não funciona para matrizes, funciona?
Toivo Säwén
57

Há uma biblioteca (chamada “clone”) , que faz isso muito bem. Ele fornece a clonagem / cópia recursiva mais completa de objetos arbitrários que eu conheço. Ele também suporta referências circulares, que ainda não são cobertas pelas outras respostas.

Você pode encontrá-lo também nas npm . Pode ser usado para o navegador e também para o Node.js.

Aqui está um exemplo de como usá-lo:

Instale-o com

npm install clone

ou empacotá-lo com Ender .

ender build clone [...]

Você também pode baixar o código-fonte manualmente.

Então você pode usá-lo no seu código fonte.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Aviso: sou o autor da biblioteca.)

pvorb
fonte
3
O clone npm foi inestimável para mim por clonar objetos aninhados arbitrariamente. Esta é a resposta certa.
Andy Ray
qual é o desempenho da sua biblioteca em comparação com, digamos JSON.parse(JSON.stringify(obj))?
Pkeck1
Aqui está uma biblioteca que afirma que existem opções mais rápidas. Ainda não testei.
Pvorb #
Boa solução e Isto suporta referências circulares (ao contrário JSON parse)
Lucas
55

Cloning Um objeto sempre foi uma preocupação em JS, mas já existia antes do ES6, listo diferentes maneiras de copiar um objeto em JavaScript abaixo, imagine que você tenha o objeto abaixo e gostaria de ter uma cópia profunda disso:

var obj = {a:1, b:2, c:3, d:4};

Existem algumas maneiras de copiar este objeto, sem alterar a origem:

1) ES5 +, usando uma função simples para fazer a cópia para você:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 +, usando JSON.parse e JSON.stringify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) SublinhadoJs & Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Espero que estas ajudem ...

Alireza
fonte
2
clone em sublinhado não é um clone de profundidade na versão atual
Rogelio
Obrigado. sim como novo documento para Sublinhado ... clone_.clone (objeto) Crie um clone superficial do objeto simples fornecido. Quaisquer objetos ou matrizes aninhados serão copiados por referência, não duplicados. _.clone ({nome: 'moe'}); => {nome: 'moe'};
Alireza
59
Object.assignse não copiar profundo. Exemplo: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Se essa fosse uma cópia profunda, y.a.bainda seria c, mas é agora d.
kba
8
Object.assign () apenas clona o primeiro nível de propriedades!
haemse
5
o que é a função cloneSO ()?
pastorello
53

Sei que este é um post antigo, mas achei que isso poderia ser de alguma ajuda para a próxima pessoa que tropeçar.

Contanto que você não atribua um objeto a nada, ele não mantém referência na memória. Portanto, para criar um objeto que você deseja compartilhar entre outros objetos, você precisará criar uma fábrica como esta:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Joe
fonte
16
Essa resposta não é realmente relevante porque a pergunta é: dada a instância b, como se cria uma cópia c, NÃO sabendo da fábrica a ou não querendo usar a fábrica a. O motivo pelo qual você pode não querer usar a fábrica é que, após a instanciação b, pode ter sido inicializado com dados adicionais (por exemplo, entrada do usuário).
Noel Abrahams
12
É verdade que isso não é realmente uma resposta para a pergunta, mas acho que é importante que ela esteja aqui, porque é a resposta para a pergunta que eu suspeito que muitas das pessoas que vêm aqui estão realmente querendo fazer.
Ponto
8
Desculpe pessoal, eu realmente não entendo por que tantos votos positivos. A clonagem de um objeto é um conceito bastante claro, você conecta um objeto de OUTRO objeto e não tem muito a ver com a criação de um novo com o padrão de fábrica.
opensas 18/08/14
2
Enquanto isso funciona para objetos predefinidos, a "clonagem" dessa maneira não reconhecerá novas propriedades adicionadas ao objeto original. Se você criar a, adicione uma nova propriedade a e crie b. b não terá a nova propriedade. Essencialmente, o padrão de fábrica é imutável a novas propriedades. Isso não é clonagem paradigmática. Veja: jsfiddle.net/jzumbrun/42xejnbx
Jon
1
Eu acho que esse é um bom conselho, geralmente, pois em vez de usar const defaultFoo = { a: { b: 123 } };você pode ir const defaultFoo = () => ({ a: { b: 123 } };e seu problema está resolvido. No entanto, realmente não é uma resposta para a pergunta. Pode ter feito mais sentido como um comentário sobre a pergunta, não uma resposta completa.
Josh de Qaribou 6/02
48

Se você estiver usando, a biblioteca Underscore.js possui um método clone .

var newObject = _.clone(oldObject);
itsadok
fonte
24
lodash tem um método cloneDeep, ele também apoiar outro param de clone para torná-lo profunda: lodash.com/docs#clone e lodash.com/docs#cloneDeep
opensas
12
@opensas concordou. Lodash é geralmente superior a sublinhado
nha
7
Defendo a exclusão dessa e de todas as outras respostas, que são apenas referências de uma linha ao .clone(...)método de uma biblioteca de utilitários . Todas as principais bibliotecas as possuem e as breves e repetidas respostas não detalhadas não são úteis para a maioria dos visitantes, que não usarão essa biblioteca em particular.
Jeremy Banks
41

Aqui está uma versão da resposta de ConroyP acima que funciona mesmo que o construtor tenha exigido parâmetros:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Esta função também está disponível na minha biblioteca simpleoo .

Editar:

Aqui está uma versão mais robusta (graças a Justin McCandless, agora isso também suporta referências cíclicas):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
Matt Browne
fonte
30

A seguir, são criadas duas instâncias do mesmo objeto. Eu encontrei e estou usando atualmente. É simples e fácil de usar.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
nathan rogers
fonte
Há algo de errado com esta resposta? É mais útil como uma solução independente, mas simples; mas a solução jQuery é mais popular. Por que é que?
ceremcem 24/09/2015
Sim, por favor me avise. Parece estar funcionando conforme o esperado, se houver alguma falha oculta em algum lugar, eu preciso usar uma solução diferente.
Nathan rogers #
4
Para um objeto simples, isso é cerca de 6 vezes mais lento no Chrome do que a resposta fornecida e fica muito mais lento à medida que a complexidade do objeto aumenta. Ele é extremamente dimensionado e pode prejudicar sua aplicação muito rapidamente.
tic
1
Você não precisa de dados, apenas uma compreensão do que está acontecendo. Essa técnica de clonagem serializa o objeto inteiro em uma sequência e analisa a serialização da sequência para criar um objeto. Inerentemente, isso será muito mais lento do que simplesmente reorganizar alguma memória (que é o que os clones mais sofisticados fazem). Mas com isso dito, para projetos de pequeno a médio porte (dependendo da sua definição de "médio porte") quem se importa se é 1000x menos eficiente? Se seus objetos são pequenos e você não os clona, ​​uma tonelada de 1000x de praticamente nada ainda é praticamente nada.
precisa
3
Além disso, esse método perde métodos (ou qualquer coisa que não seja permitida no JSON), além de - JSON.stringify converterá objetos Date em seqüências de caracteres, ... e não o contrário;) Fique de fora desta solução.
MT MT
22

Crockford sugere (e eu prefiro) usar esta função:

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

var newObject = object(oldObject);

É conciso, funciona como esperado e você não precisa de uma biblioteca.


EDITAR:

Este é um polyfill para Object.create, portanto, você também pode usá-lo.

var newObject = Object.create(oldObject);

NOTA: Se você usar um pouco disso, poderá ter problemas com alguma iteração usada hasOwnProperty. Porque, createcrie um novo objeto vazio que herda oldObject. Mas ainda é útil e prático para clonar objetos.

Por exemplo, se oldObject.a = 5;

newObject.a; // is 5

mas:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
protonfish
fonte
9
me corrija se eu estiver errado, mas essa não é a função gerada por Crockford para herança prototípica? Como isso se aplica ao clone?
Alex Nolasco
3
Sim, eu estava com medo dessa discussão: qual é a diferença prática entre clone, cópia e herança prototípica, quando você deve usar cada uma e quais funções nesta página estão realmente fazendo o que? Encontrei esta página SO pesquisando "objeto de cópia javascript" no Google. O que eu realmente estava procurando era a função acima, então voltei a compartilhar. Meu palpite é que o solicitante estava procurando por isso também.
Chris Broski
51
A diferença entre clone / cópia e herança é que, usando o seu exemplo, quando altero uma propriedade de oldObject, a propriedade também é alterada em newObject. Se você fizer uma cópia, poderá fazer o que quiser com oldObject sem alterar newObject.
Ridcully
13
Isso interromperá a verificação hasOwnProperty, por isso é uma maneira bastante invasiva de clonar um objeto e fornecer resultados inesperados.
Corban Brook
var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Cody
22

O Lodash possui um bom método _.cloneDeep (value) :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
abertura
fonte
5
Defendo a exclusão dessa e de todas as outras respostas, que são apenas referências de uma linha ao .clone(...)método de uma biblioteca de utilitários . Todas as principais bibliotecas as possuem e as breves e repetidas respostas não detalhadas não são úteis para a maioria dos visitantes, que não usarão essa biblioteca em particular.
Jeremy Banks
Uma maneira mais fácil é usar _.merge({}, objA). Se pelo menos o lodash não modificasse objetos, a clonefunção não seria necessária.
Rebs 27/02
7
As pesquisas do Google por clonagem de objetos JS se referem aqui. Estou usando o Lodash, então esta resposta é relevante para mim. Não vamos todos "deletionist wikipedia" em respostas por favor.
Rebs 27/02
2
No Nó 9, JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) é muito mais rápido que _.deepClone (arrayOfAbout5KFlatObjects).
Dan Dascalescu
21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
Mark Cidade
fonte
17
O problema com o método é que, se você tiver subobjetos no objeto, suas referências serão clonadas, e não os valores de cada subobjeto.
214 Kamarey
1
basta torná-lo recursivo para que os subobjetos sejam clonados profundamente.
Fiatjaf
apenas curioso ... não será a variável clone terá os ponteiros para as propriedades do objeto original? porque parece nenhuma nova alocação de memória
Rupesh Patel
3
Sim. Como é apenas uma cópia superficial, o clone apontará exatamente para os mesmos objetos apontados pelo objeto original.
Mark Cidade
Esta não é uma resposta. Você está literalmente apenas enchendo um objeto com referências a outro objeto. Fazer alterações no objeto de origem fará alterações no "clone".
Shawn Whinnery 20/03/19
19

Cópia única de uma cópia ( ECMAScript 5th edition ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Uma cópia simples e rasa ( ECMAScript 6ª edição , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true
Maël Nison
fonte
6
Isso pode ser bom para objetos simples, mas apenas copia valores de propriedade. Ele não toca na cadeia do protótipo e, ao usá- Object.keyslo, ignora as propriedades não enumeráveis ​​e herdadas. Além disso, ele perde os descritores de propriedade ao fazer a atribuição direta.
Matt Bierner
Se você copiar o protótipo também, estará faltando apenas não enumeráveis ​​e descritores de propriedades, sim? Muito bom. :)
sam
Desempenho à parte, essa é uma maneira realmente conveniente de copiar superficialmente um objeto. Costumo usar isso para classificar propriedades de descanso falsas em uma tarefa de desestruturação nos meus componentes do React.
Mjohnsonengr 17/03/16
17

Só porque eu não vi o AngularJS mencionado e pensei que as pessoas poderiam querer saber ...

angular.copy também fornece um método de cópia profunda de objetos e matrizes.

Dan Atkinson
fonte
ou pode ser usado da mesma maneira que o jQiery extend:angular.extend({},obj);
Galvani
2
@ Galvani: Note-se que jQuery.extende angular.extendsão cópias rasas. angular.copyé uma cópia profunda.
Dan Atkinson
16

Parece ainda não haver um operador de clone profundo ideal para objetos do tipo array. Como o código abaixo ilustra, o clonador jQuery de John Resig transforma matrizes com propriedades não numéricas em objetos que não são matrizes e o clonador JSON do RegDwight remove as propriedades não numéricas. Os testes a seguir ilustram esses pontos em vários navegadores:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)
Notas da página
fonte
14
como outros salientaram nos comentários à resposta de Resig, se você deseja clonar um objeto semelhante a um array, altera {} para [] na chamada de extensão, por exemplo, jQuery.extend (true, [], obj)
Anentropic
15

Eu tenho duas boas respostas, dependendo se seu objetivo é clonar um "objeto JavaScript antigo simples" ou não.

Vamos supor também que sua intenção é criar um clone completo sem referências de protótipo para o objeto de origem. Se você não estiver interessado em um clone completo, poderá usar muitas das rotinas Object.clone () fornecidas em algumas das outras respostas (padrão de Crockford).

Para objetos JavaScript simples e antigos, uma boa maneira de clonar um objeto em tempos de execução modernos é muito simples:

var clone = JSON.parse(JSON.stringify(obj));

Observe que o objeto de origem deve ser um objeto JSON puro. Isto é, todas as suas propriedades aninhadas devem ser escalares (como booleano, string, array, objeto, etc.). Quaisquer funções ou objetos especiais como RegExp ou Date não serão clonados.

É eficiente? Claro que sim. Tentamos todos os tipos de métodos de clonagem e isso funciona melhor. Tenho certeza que algum ninja poderia invocar um método mais rápido. Mas suspeito que estamos falando de ganhos marginais.

Essa abordagem é simples e fácil de implementar. Envolva-o em uma função de conveniência e, se você realmente precisar obter algum ganho, tente mais tarde.

Agora, para objetos JavaScript não comuns, não há uma resposta realmente simples. De fato, não pode haver por causa da natureza dinâmica das funções JavaScript e do estado interno do objeto. A clonagem profunda de uma estrutura JSON com funções internas requer que você recrie essas funções e seu contexto interno. E o JavaScript simplesmente não tem uma maneira padronizada de fazer isso.

A maneira correta de fazer isso, mais uma vez, é através de um método de conveniência que você declara e reutiliza dentro do seu código. O método de conveniência pode ser dotado de alguma compreensão de seus próprios objetos, para que você possa recriar adequadamente o gráfico dentro do novo objeto.

Nós escrevemos por conta própria, mas a melhor abordagem geral que já vi é abordada aqui:

http://davidwalsh.name/javascript-clone

Essa é a ideia certa. O autor (David Walsh) comentou a clonagem de funções generalizadas. Isso é algo que você pode optar por fazer, dependendo do seu caso de uso.

A idéia principal é que você precisa lidar especialmente com a instanciação de suas funções (ou classes prototípicas, por assim dizer) por tipo. Aqui, ele forneceu alguns exemplos para RegExp e Date.

Este código não é apenas breve, mas também é muito legível. É bem fácil de estender.

Isso é eficiente? Claro que sim. Como o objetivo é produzir um verdadeiro clone de cópia profunda, você precisará percorrer os membros do gráfico do objeto de origem. Com essa abordagem, você pode ajustar exatamente quais membros filhos tratar e como lidar manualmente com tipos personalizados.

Então lá vai você. Duas abordagens. Ambos são eficientes na minha opinião.

Michael Uzquiano
fonte
13

Geralmente, essa não é a solução mais eficiente, mas faz o que eu preciso. Casos de teste simples abaixo ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Teste de matriz cíclica ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Teste de funcionamento...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
neatonk
fonte
11

AngularJS

Bem, se você estiver usando angular, você também pode fazer isso

var newObject = angular.copy(oldObject);
azerafati
fonte
11

Discordo da resposta com os melhores votos aqui . Um Deep Clone recursivo é muito mais rápido que a abordagem JSON.parse (JSON.stringify (obj)) mencionada.

E aqui está a função para referência rápida:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}
programador
fonte
2
Gostei dessa abordagem, mas ela não lida com datas adequadamente; considere adicionar algo como if(o instanceof Date) return new Date(o.valueOf());depois de verificar se existe um valor nulo `
Luis
Falhas em referências circulares.
Harry
No mais recente Firefox estável, isso é muito mais longo do que as outras estratégias no link Jsben.ch, por uma ordem de magnitude ou mais. Vence os outros na direção errada.
WBT
11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};
Dima
fonte
10

Somente quando você pode usar o ECMAScript 6 ou transpilers .

Recursos:

  • Não acionará o getter / setter durante a cópia.
  • Preserva o getter / setter.
  • Preserva informações de protótipo.
  • Funciona com estilos de escrita OO literal e objeto- funcional .

Código:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}
andrew
fonte
9

Aqui está um método abrangente clone () que pode clonar qualquer objeto JavaScript. Ele lida com quase todos os casos:

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
Ele converte primitivos em objetos de invólucro, o que não é uma boa solução na maioria dos casos.
Danubian Sailor
@DanubianSailor - acho que não ... parece retornar primitivos imediatamente desde o início, e não parece estar fazendo nada com eles que os transformaria em objetos wrapper à medida que são retornados.
Jimbo Jonny