javascript - without - typescript clone object




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

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

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

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


Nota: Esta é uma resposta a outra resposta, não uma resposta adequada a esta pergunta. Se você deseja clonagem rápida de objetos, siga o conselho do Corban em sua resposta a esta pergunta.

Quero observar que o método .clone() no jQuery apenas clona elementos DOM. Para clonar objetos JavaScript, você faria:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Mais informações podem ser encontradas na documentação do jQuery .

Eu também quero observar que a cópia profunda é realmente muito mais inteligente do que a mostrada acima - ela é capaz de evitar muitos traps (tentando estender profundamente um elemento DOM, por exemplo). É usado com frequência no núcleo do jQuery e em plugins com grande efeito.


A maneira eficiente de clonar (não clonar profundamente) um objeto em uma linha de código

Um método Object.assign 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 próprias enumeráveis ​​de um ou mais objetos de origem para um objeto de destino.

Object.assign

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

AngularJS

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

var newObject = angular.copy(oldObject);

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

Finalize este benchmark: http://jsben.ch/#/bWfk9

Nos meus testes anteriores, onde a velocidade era uma das principais preocupações que encontrei

JSON.parse(JSON.stringify(obj))

para ser o caminho mais rápido para clonar um objeto em profundidade (ele supera jQuery.extend com deep flag definido como true em 10-20%).

jQuery.extend é bastante rápido quando o sinalizador profundo é definido como falso (clone superficial). É uma boa opção, porque inclui alguma lógica extra para validação de tipo e não copia propriedades indefinidas, etc., mas isso também o atrasa um pouco.

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

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

Os mecanismos de rastreio do JavaScript são inadequados para otimizar os loops em..in e a verificação de hasOwnProperty também o atrasará. Clone manual quando a velocidade é uma necessidade absoluta.

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

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

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


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. Também suporta referências circulares, que ainda não são cobertas pelas outras respostas.

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

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

Instale-o com

npm install clone

ou empacote com Ender .

ender build clone [...]

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

Então você pode usá-lo em 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' } }

(Isenção de responsabilidade: Eu sou o autor da biblioteca.)


Se não houvesse nenhum embutido, você poderia 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;
}

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

var newObject = JSON.parse(JSON.stringify(oldObject));

Cópia detalhada por performance: Classificação do melhor ao pior

  • Reatribuição "=" (matrizes de cadeias, matrizes de números - apenas)
  • Fatia (matrizes de string, matrizes de números - somente)
  • Concatenação (matrizes de string, matrizes de números - somente)
  • Função personalizada: para loop ou cópia recursiva
  • $ .extend do jQuery
  • JSON.parse (matrizes de string, matrizes numéricas, matrizes de objetos - somente)
  • Underscore.js _.clone (matrizes de strings, matrizes de números - somente)
  • Lo-Dash's _.cloneDeep

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

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

Onde a transferência tem o desempenho mais rápido:

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

E o .slice () tem um desempenho melhor que o .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

Copie profundamente uma matriz de objetos (dois ou mais níveis - ponteiros de referência):

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

Escreva uma função personalizada (tem 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 de utilitário de terceiros:

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

Onde $ .extend do jQuery tem melhor desempenho:


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

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

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 do Lo-Dash _.cloneDeep lodash

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.Usando o Underscore.js _.clone link 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.)

Referência medium.com

Parque de Benchmarking de Desempenho do JSBEN.CH 1 ~ 3 http://jsben.ch/KVQLd


Crockford sugere (e eu prefiro) usar essa 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, então você também pode usar isso.

var newObject = Object.create(oldObject);

OBSERVAÇÃO: Se você usar um pouco disso, poderá ter problemas com alguma iteração que use 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

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

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

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

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

Observe que o objeto de origem deve ser um objeto JSON puro. Isso quer dizer que 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. Nós tentamos todos os tipos de métodos de clonagem e isso funciona melhor. Tenho certeza que alguns ninjas poderiam conjurar 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 extrair algum ganho, vá para um momento posterior.

Agora, para objetos JavaScript não simples, não há uma resposta realmente simples. Na verdade, não pode haver por causa da natureza dinâmica das funções JavaScript e do estado do objeto interno. A clonagem profunda de uma estrutura JSON com funções internas exige 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, é por meio de um método de conveniência que você declara e reutiliza em seu código. O método de conveniência pode ser dotado de alguma compreensão de seus próprios objetos, de modo que você possa recriar adequadamente o gráfico dentro do novo objeto.

Nós escrevemos os nossos, mas a melhor abordagem geral que eu 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 escolher, dependendo do seu caso de uso.

A idéia principal é que você precisa lidar com a instanciação de suas funções (ou classes protótipos, por assim dizer) de acordo com cada tipo. Aqui, ele forneceu alguns exemplos de RegExp e Date.

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

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

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


Cópia curta de uma linha ( ECMAScript 5ª edição ):

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

E uma cópia rasa de uma linha ( 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

Esta não é geralmente 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

Lodash tem um bom método de lodash.com/docs#cloneDeep :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

O seguinte cria 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));

Parece não haver nenhum operador de clone profundo ideal para objetos semelhantes a array. Como o código abaixo ilustra, o jQuery cloner de John Resig transforma matrizes com propriedades não-numéricas em objetos que não são matrizes, e o cloner 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)

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

angular.copy também fornece um método de copiar profundamente objetos e matrizes.


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

function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }




clone