¿Cuál es la forma más eficiente de clonar en profundidad un objeto en JavaScript?

5180

¿Cuál es la forma más eficiente de clonar un objeto JavaScript? He visto que obj = eval(uneval(o));se usa, pero eso no es estándar y solo es compatible con Firefox .

He hecho cosas como obj = JSON.parse(JSON.stringify(o));pero cuestionar la eficiencia.

También he visto funciones de copia recursiva con varios defectos.
Me sorprende que no exista una solución canónica.

jschrab
fuente
566
Eval no es malvada. Usar eval mal es. Si tienes miedo de sus efectos secundarios, lo estás usando mal. Los efectos secundarios que temes son las razones para usarlo. ¿Alguien respondió a tu pregunta?
James
15
Clonar objetos es un asunto complicado, especialmente con objetos personalizados de colecciones arbitrarias. Probablemente por eso no hay una forma original de hacerlo.
b01
12
eval()generalmente es una mala idea porque muchos optimizadores del motor Javascript tienen que apagarse cuando se manejan variables que se configuran a través deeval . Solo tener eval()en su código puede conducir a un peor rendimiento.
user56reinstatemonica8
2
Posible duplicado de la forma más elegante de clonar un objeto JavaScript
John Slegers
12
Tenga en cuenta que el JSONmétodo perderá cualquier tipo de Javascript que no tenga equivalente en JSON. Por ejemplo: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))generará{a: null, b: null, c: null, g: false}
oriadam

Respuestas:

4731

Clonación profunda nativa

Se llama "clonación estructurada", funciona experimentalmente en el Nodo 11 y posteriores, y con suerte aterrizará en los navegadores. Vea esta respuesta para más detalles.

Clonación rápida con pérdida de datos - JSON.parse / stringify

Si no se utiliza Dates, funciones undefined, Infinity, expresiones regulares, Mapas, Conjuntos, gotas, filelists, ImageDatas, matrices dispersas, Conjuntos escritos u otros tipos complejos dentro de su objeto, un revestimiento muy simple uno a lo profundo del clon de un objeto es:

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

Ver la respuesta de Corban para puntos de referencia.

Clonación confiable usando una biblioteca

Dado que la clonación de objetos no es trivial (tipos complejos, referencias circulares, funciones, etc.), la mayoría de las bibliotecas principales proporcionan funciones para clonar objetos. No reinvente la rueda : si ya está utilizando una biblioteca, verifique si tiene una función de clonación de objetos. Por ejemplo,

ES6

Para completar, tenga en cuenta que ES6 ofrece dos mecanismos de copia superficial: Object.assign()y la sintaxis de propagación . que copia los valores de todas las propiedades propias enumerables de un objeto a otro. Por ejemplo:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax
Dan Dascalescu
fuente
77
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js en la línea 276 (hay un poco de código que hace otra cosa pero el código de "cómo hacer esto en JS" está ahí :)
Rune FS
77
Aquí está el código JS detrás de la copia profunda de jQuery, para cualquier persona interesada: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W
194
Woah! Solo para ser súper claro: no tengo idea de por qué esta respuesta fue elegida como la respuesta correcta, esta fue una respuesta a las respuestas que figuran a continuación: stackoverflow.com/a/122190/6524 (que estaba recomendando .clone(), que no es el código correcto para ser utilizando en este contexto). Desafortunadamente, esta pregunta ha pasado por tantas revisiones que la discusión original ya no es aparente. Por favor, solo siga los consejos de Corban y escriba un bucle o copie las propiedades directamente a un nuevo objeto, si le importa la velocidad. ¡O pruébalo por ti mismo!
John Resig
99
Esta es una pregunta de JavaScript (sin mención de jQuery).
gphilip
6060
¿Cómo se podría hacer esto sin usar jQuery?
Awesomeness01
2266

Consulte este punto de referencia: http://jsben.ch/#/bWfk9

En mis pruebas anteriores, donde la velocidad era una preocupación principal, encontré

JSON.parse(JSON.stringify(obj))

para ser la forma más lenta de clonar en profundidad un objeto (es más lento que jQuery.extend con el deepindicador establecido verdadero en un 10-20%).

jQuery.extend es bastante rápido cuando la deepbandera se establece en false(clon superficial). Es una buena opción, porque incluye algo de lógica adicional para la validación de tipos y no copia sobre propiedades indefinidas, etc., pero esto también lo ralentizará un poco.

Si conoce la estructura de los objetos que intenta clonar o puede evitar matrices anidadas profundas, puede escribir un for (var i in obj)bucle simple para clonar su objeto mientras verifica hasOwnProperty y será mucho más rápido que jQuery.

Por último, si está intentando clonar una estructura de objeto conocida en un bucle activo, puede obtener MUCHO MÁS RENDIMIENTO simplemente integrando el procedimiento de clonación y construyendo manualmente el objeto.

Los motores de rastreo de JavaScript son muy for..inmalos para optimizar los bucles y comprobar hasOwnProperty también lo ralentizará. Clonación manual cuando la velocidad es una necesidad absoluta.

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

Tenga cuidado al usar el JSON.parse(JSON.stringify(obj))método en Dateobjetos: JSON.stringify(new Date())devuelve una representación de cadena de la fecha en formato ISO, que JSON.parse() no se convierte de nuevo en un Dateobjeto. Vea esta respuesta para más detalles .

Además, tenga en cuenta que, al menos en Chrome 65, la clonación nativa no es el camino a seguir. De acuerdo con JSPerf, realizar la clonación nativa mediante la creación de una nueva función es casi 800x más lento que usar JSON.stringify que es increíblemente rápido todo el camino a través del tablero.

Actualización para ES6

Si está utilizando Javascript ES6, pruebe este método nativo para la clonación o copia superficial.

Object.assign({}, obj);
Corban Brook
fuente
44
@trysis Object.create no está clonando el objeto, está utilizando el objeto prototipo ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser
105
Este método también eliminará los keysde su object, que tienen functionscomo valores, porque JSONno admite funciones.
Karlen Kishmiryan
39
También tenga en cuenta que el uso JSON.parse(JSON.stringify(obj))de Objetos de fecha también convertirá la fecha a UTC en la representación de cadena en el formato ISO8601 .
dnlgmzddr
31
El enfoque JSON también se ahoga en referencias circulares.
rico remer
28
@velop, Object.assign ({}, objToClone) parece que hace un clon superficial, sin embargo, al usarlo mientras se juega en la consola de herramientas de desarrollo, el clon del objeto todavía apuntaba a una referencia del objeto clonado. Así que no creo que sea realmente aplicable aquí.
Garrett Simpson, el
473

Suponiendo que solo tiene variables y no funciones en su objeto, puede usar:

var newObject = JSON.parse(JSON.stringify(oldObject));
Sultan Shakir
fuente
86
El inconveniente de este enfoque, como acabo de encontrar, es que si su objeto tiene alguna función (la mía tiene captadores y establecedores internos), estas se pierden cuando se encadenan ... Si eso es todo lo que necesita, este método está bien ...
Markive
31
@ Jason, la razón por la cual este método es más lento que la copia superficial (en un objeto profundo) es que este método, por definición, es una copia profunda. Pero como JSONse implementa en el código nativo (en la mayoría de los navegadores), esto será considerablemente más rápido que el uso de cualquier otra solución de copia profunda basada en javascript, y a veces puede ser más rápido que una técnica de copia superficial basada en javascript (ver: jsperf.com/cloning -un objeto / 79 ).
MiJyn
35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer
32
Esta técnica va a destruir también todos los Dateobjetos que están almacenados dentro del objeto, convirtiéndolos en forma de cadena.
fstab
13
No podrá copiar nada que no sea parte de la especificación JSON ( json.org )
cdmckay
397

Clonación Estructurada

El estándar HTML incluye un algoritmo de clonación / serialización estructurado interno que puede crear clones profundos de objetos. Todavía está limitado a ciertos tipos integrados, pero además de los pocos tipos admitidos por JSON, también es compatible con Fechas, RegExps, Mapas, Conjuntos, Blobs, Listas de archivos, ImageDatas, Matrices dispersas, Matrices escritas, y probablemente más en el futuro . También conserva referencias dentro de los datos clonados, lo que le permite soportar estructuras cíclicas y recursivas que causarían errores para JSON.

Soporte en Node.js: Experimental 🙂

El v8módulo en Node.js actualmente (a partir del Nodo 11) expone la API de serialización estructurada directamente , pero esta funcionalidad todavía está marcada como "experimental" y está sujeta a cambios o eliminación en futuras versiones. Si está utilizando una versión compatible, clonar un objeto es tan simple como:

const v8 = require('v8');

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

Soporte directo en navegadores: ¿quizás eventualmente? 😐

Actualmente, los navegadores no proporcionan una interfaz directa para el algoritmo de clonación estructurada, pero structuredClone()se ha discutido una función global en whatwg / html # 793 en GitHub . Como se propone actualmente, usarlo para la mayoría de los propósitos sería tan simple como:

const clone = structuredClone(original);

A menos que se envíe esto, las implementaciones estructuradas de clones de los navegadores solo se exponen indirectamente.

Solución asíncrona: utilizable. 😕

La forma más económica de crear un clon estructurado con las API existentes es publicar los datos a través de un puerto de un MessageChannels . El otro puerto emitirá un messageevento con un clon estructurado del adjunto .data. Desafortunadamente, escuchar estos eventos es necesariamente asíncrono, y las alternativas sincrónicas son menos prácticas.

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

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

Soluciones alternativas sincrónicas: ¡horrible! 🤢

No hay buenas opciones para crear clones estructurados sincrónicamente. Aquí hay un par de trucos poco prácticos en su lugar.

history.pushState()y history.replaceState()ambos crean un clon estructurado de su primer argumento y le asignan ese valor history.state. Puede usar esto para crear un clon estructurado de cualquier objeto como este:

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

Ejemplo de uso:

Aunque sincrónico, esto puede ser extremadamente lento. Se incurre en todos los gastos generales asociados con la manipulación del historial del navegador. Llamar a este método repetidamente puede hacer que Chrome deje de responder temporalmente.

El Notificationconstructor crea un clon estructurado de sus datos asociados. También intenta mostrar una notificación del navegador al usuario, pero esto fallará silenciosamente a menos que haya solicitado permiso de notificación. En caso de que tenga el permiso para otros fines, cerraremos inmediatamente la notificación que hemos creado.

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

Ejemplo de uso:

Jeremy Banks
fuente
3
@rynah Acabo de revisar la especificación nuevamente y tienes razón: los métodos history.pushState()y se history.replaceState()configuran sincrónicamente history.stateen un clon estructurado de su primer argumento. Un poco raro, pero funciona. Estoy actualizando mi respuesta ahora.
Jeremy Banks
40
¡Esto está muy mal! Esa API no debe usarse de esta manera.
Fardin K.
209
Como el tipo que implementó pushState en Firefox, siento una extraña mezcla de orgullo y repulsión por este truco. Bien hecho muchachos.
Justin L.
pushState o Notification hack no funciona para algunos tipos de objetos como Function
Shishir Arora
323

Si no hubiera ninguno incorporado, podría intentar:

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
fuente
20
La solución JQuery funcionará para elementos DOM pero no para cualquier objeto. Mootools tiene el mismo límite. Ojalá tuvieran un "clon" genérico para cualquier objeto ... La solución recursiva debería funcionar para cualquier cosa. Probablemente sea el camino a seguir.
jschrab
55
Esta función se rompe si el objeto que se está clonando tiene un constructor que requiere parámetros. Parece que podemos cambiarlo a "var temp = new Object ()" y hacer que funcione en todos los casos, ¿no?
Andrew Arnott
3
Andrew, si lo cambias a var temp = new Object (), entonces tu clon no tendrá el mismo prototipo que el objeto original. Intente usar: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder
1
Similar a la respuesta de limscoder, vea mi respuesta a continuación sobre cómo hacer esto sin llamar al constructor: stackoverflow.com/a/13333781/560114
Matt Browne
3
Para los objetos que contienen referencias a subpartes (es decir, redes de objetos), esto no funciona: si dos referencias apuntan al mismo subobjeto, la copia contiene dos copias diferentes del mismo. Y si hay referencias recursivas, la función nunca terminará (bueno, al menos no de la manera que lo desee :-) Para estos casos generales, debe agregar un diccionario de objetos ya copiados y verificar si ya lo copió. ... La programación es compleja cuando se usa un lenguaje simple
virtualnobi
153

La manera eficiente de clonar (no clonar en profundidad) un objeto en una línea de código

Un Object.assignmétodo es parte del estándar ECMAScript 2015 (ES6) y hace exactamente lo que necesita.

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

El método Object.assign () se utiliza para copiar los valores de todas las propiedades propias enumerables de uno o más objetos de origen a un objeto de destino.

Lee mas...

El polyfill para admitir navegadores antiguos:

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
fuente
82
Esto no se copia de forma recursiva, por lo que realmente no ofrece una solución al problema de clonar un objeto.
mwhite
55
Este método funcionó, aunque probé algunos y _.extend ({}, (obj)) fue, con diferencia, el más rápido: 20 veces más rápido que JSON.parse y 60% más rápido que Object.assign, por ejemplo. Copia todos los subobjetos bastante bien.
Nico
11
@mwhite hay una diferencia entre clonar y clonar profundo. De hecho, esta respuesta se clona, ​​pero no se clona en profundidad.
Meirion Hughes
57
El operador pidió un clon profundo. Esto no hace clon profundo.
user566245
99
¡Este método hace una copia BAJA , y no una copia PROFUNDA ! ¡Debido a esto, es una respuesta completamente incorrecta !
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;
}

Prueba:

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);
revs Kamarey
fuente
3
¿qué pasa var obj = {}yobj.a = obj
neaumusic
55
No entiendo esta función. Supongamos que from.constructores Datepor ejemplo. ¿Cómo se alcanzará la tercera ifprueba cuando la segunda prueba tenga iféxito y haga que la función regrese (desde Date != Object && Date != Array)?
Adam McKee
1
@AdamMcKee Porque el argumento de javascript que pasa y la asignación de variables es complicado . Este enfoque funciona muy bien, incluidas las fechas (que de hecho son manejadas por la segunda prueba): violín para probar aquí: jsfiddle.net/zqv9q9c6 .
brichins
1
@NickSweeting: Intenta, puede que funcione. Si no es así, corríjalo y actualice la respuesta. Así es como funciona aquí en la comunidad :)
Kamarey
1
Esta función no clona la expresión regular en la prueba, la condición "from.constructor! = Object && from.constructor! = Array" siempre devuelve verdadero para otros constructores como Number, Date, etc.
aMarCruz
95

Esto es lo que estoy 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
fuente
8
Esto no parece correcto. cloneObject({ name: null })=>{"name":{}}
Niyaz
13
Esto se debe a otra cosa tonta en javascript, typeof null > "object"pero Object.keys(null) > TypeError: Requested keys of a value that is not an object.cambia la condición aif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us
Esto asignará propiedades enumerables heredadas de obj directamente al clon, y supone que obj es un Objeto simple.
RobG
Esto también arruina las matrices, que se convierten en objetos con teclas numéricas.
cuchilla
No es un problema si no usa nulo.
Jorge Bucaran
78

Copia profunda por rendimiento: clasificado de mejor a peor

  • Reasignación "=" (matrices de cadenas, matrices de números - solo)
  • Slice (matrices de cadenas, matrices de números - solo)
  • Concatenación (matrices de cadenas, matrices de números - solo)
  • Función personalizada: for-loop o copia recursiva
  • $ .extend de jQuery
  • JSON.parse (matrices de cadenas, matrices de números, matrices de objetos - solo)
  • _.Clone de Underscore.js (matrices de cadenas, matrices de números - solo)
  • _ -CloneDeep de Lo-Dash

Copie en profundidad una matriz de cadenas o números (un nivel, sin punteros de referencia):

Cuando una matriz contiene números y cadenas, funciones como .slice (), .concat (), .splice (), el operador de asignación "=" y la función de clonación de Underscore.js; hará una copia profunda de los elementos de la matriz.

Donde la reasignación tiene el rendimiento más rápido:

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

Y .slice () tiene un mejor rendimiento 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

Copie en profundidad una matriz de objetos (dos o más niveles - punteros de referencia):

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

Escriba una función personalizada (tiene un rendimiento más rápido que $ .extend () o 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 funciones de utilidad de terceros:

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

Donde $ .extend de jQuery tiene mejor rendimiento:

tfmontague
fuente
Probé algunos y _.extend ({}, (obj)) fue, con diferencia, el más rápido: 20 veces más rápido que JSON.parse y 60% más rápido que Object.assign, por ejemplo. Copia todos los subobjetos bastante bien.
Nico
44
Todos sus ejemplos son superficiales, de un nivel. Esta no es una buena respuesta. La pregunta se refería a la clonación profunda, es decir, al menos dos niveles.
Karl Morrison
1
Una copia profunda es cuando un objeto se copia en su totalidad sin el uso de punteros de referencia a otros objetos. Las técnicas de la sección "Copiar en profundidad una matriz de objetos", como jQuery.extend () y la función personalizada (que es recursiva) copian objetos con "al menos dos niveles". Entonces, no, no todos los ejemplos son copias de "un nivel".
tfmontague
1
Me gusta su función de copia personalizada, pero debe excluir los valores nulos, de lo contrario todos los valores nulos se están convirtiendo en objetos, es decir:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi
2
@HossamMourad: Josi corrigió el error el 1 de febrero (en el comentario anterior) y no pude actualizar correctamente la respuesta. Lo sentimos, este error resultó en una refactorización de su 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
fuente
Buena respuesta pero esto falla para referencias circulares.
Lucas
59

Copia profunda de objetos en JavaScript (creo que el mejor y el más simple)

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.Utilizando el método creado

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 el enlace _.cloneDeep de Lo-Dash 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 el 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 }  

PERO MAL CUANDO

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

PERO MAL CUANDO

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 Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Rendimiento Copia profunda de objetos en JavaScript

Tính Ngô Quang
fuente
55
Object.assign()no realiza una copia profunda
Roymunson
1
debe agregar puntos de referencia para estos; eso sería muy útil
jcollum
cuando utilicé el "método creado" en un objeto que contiene una matriz que no pude usar pop () o splice (), no entiendo por qué. let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();arroja: TypeError: tmp.title.pop is not a function(por supuesto, pop () funciona bien si solo do let tmp = data; pero luego no puedo modificar tmp sin afectar los datos)
hugogogo
Oye, tu último ejemplo está mal. En mi opinión, debe usar _clone y no _cloneDeep para el ejemplo incorrecto.
kenanyildiz
Este método creado (2.) no funcionará para las matrices, ¿verdad?
Toivo Säwén
57

Hay una biblioteca (llamada "clon") , que hace esto bastante bien. Proporciona la clonación / copia recursiva más completa de objetos arbitrarios que conozco. También admite referencias circulares, que aún no están cubiertas por las otras respuestas.

También puedes encontrarlo en npm . Se puede usar tanto para el navegador como para Node.js.

Aquí hay un ejemplo sobre cómo usarlo:

Instalarlo con

npm install clone

o empaquétalo con Ender .

ender build clone [...]

También puede descargar el código fuente manualmente.

Entonces puedes usarlo en tu código fuente.

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

(Descargo de responsabilidad: soy el autor de la biblioteca).

revs pvorb
fuente
3
npm clone ha sido invaluable para mí para clonar objetos anidados arbitrariamente. Esta es la respuesta correcta.
Andy Ray
¿Cuál es el rendimiento de su lib en comparación con digamos JSON.parse(JSON.stringify(obj))?
pkyeck
Aquí hay una biblioteca que indica que hay opciones más rápidas. Aunque no lo he probado.
pvorb
Buena solución y esto admite referencias circulares (a diferencia del análisis JSON)
Lucas
55

Cloning un objeto siempre fue una preocupación en JS, pero se trataba de antes de ES6, enumero diferentes formas de copiar un objeto en JavaScript a continuación, imagina que tienes el objeto a continuación y me gustaría tener una copia profunda de eso:

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

Hay pocas formas de copiar este objeto, sin cambiar el origen:

1) ES5 +, utilizando una función simple para hacer la copia por usted:

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 +, utilizando JSON.parse y 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) subrayado Js y Loadash:

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

Espero que esto ayude ...

Alireza
fuente
2
clonar en guión bajo no es un clon profundo en la versión actual
Rogelio
Gracias. sí como nuevo documento para el subrayado ... clone_.clone (objeto) Crea un clon copiado superficialmente del objeto plano proporcionado. Cualquier objeto o conjunto anidado se copiará por referencia, no se duplicará. _.clone ({nombre: 'moe'}); => {nombre: 'moe'};
Alireza
59
Object.assignno no copiar profunda. Ejemplo: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Si esto fuera una copia profunda, lo y.a.bseguiría siendo c, pero es ahora d.
kba
8
¡Object.assign () solo clona el primer nivel de propiedades!
haemse 21/0717
55
¿Qué es la función cloneSO ()?
pastorello
53

Sé que esta es una publicación antigua, pero pensé que podría ser de alguna ayuda para la próxima persona que tropieza.

Mientras no asignes un objeto a nada, no mantendrá ninguna referencia en la memoria. Por lo tanto, para crear un objeto que desee compartir entre otros objetos, deberá crear una fábrica como esta:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Joe
fuente
16
Esta respuesta no es realmente relevante porque la pregunta es: dada la instancia b, ¿cómo se crea una copia c MIENTRAS no se conoce la fábrica a o no se quiere usar la fábrica a. La razón por la que uno no quiere usar la fábrica es que después de la instanciación b puede haberse inicializado con datos adicionales (por ejemplo, entrada del usuario).
Noel Abrahams
12
Es cierto que esta no es realmente una respuesta a la pregunta, pero creo que es importante que esté aquí porque es la respuesta a la pregunta que sospecho que muchas de las personas que vienen aquí realmente tienen la intención de hacer.
Semicolon
8
Lo siento chicos, realmente no entiendo por qué tantos votos a favor. Clonar un objeto es un concepto bastante claro, conoces un objeto de OTRO objeto, y no tiene mucho que ver con crear uno nuevo con el patrón de fábrica.
abre el
2
Si bien esto funciona para objetos predefinidos, la "clonación" de esta manera no reconocerá las nuevas propiedades agregadas al objeto original. Si crea a, agregue una nueva propiedad a a, luego cree b. b no tendrá la nueva propiedad. Esencialmente, el patrón de fábrica es inmutable a las nuevas propiedades. Esto no es paradigmáticamente la clonación. Ver: jsfiddle.net/jzumbrun/42xejnbx
Jon
1
Creo que este es un buen consejo, en general, ya que en lugar de usarlo const defaultFoo = { a: { b: 123 } };puedes ir const defaultFoo = () => ({ a: { b: 123 } };y tu problema está resuelto. Sin embargo, realmente no es una respuesta a la pregunta. Podría haber tenido más sentido como un comentario sobre la pregunta, no como una respuesta completa.
Josh de Qaribou
48

Si lo está utilizando, la biblioteca Underscore.js tiene un método de clonación .

var newObject = _.clone(oldObject);
itsadok
fuente
24
lodash tiene un método cloneDeep, también admite otro parámetro para clonar para que sea más profundo: lodash.com/docs#clone y lodash.com/docs#cloneDeep
abre el
12
@opensas estuvo de acuerdo. Lodash es generalmente superior al guión bajo
nha
77
Recomiendo eliminar esta y todas las otras respuestas que son solo referencias de una línea al .clone(...)método de una biblioteca de utilidad . Todas las bibliotecas principales las tendrán, y las breves y repetidas respuestas no detalladas no son útiles para la mayoría de los visitantes, que no utilizarán esa biblioteca en particular.
Jeremy Banks
41

Aquí hay una versión de la respuesta anterior de ConroyP que funciona incluso si el constructor ha requerido 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 función también está disponible en mi biblioteca simpleoo .

Editar:

Aquí hay una versión más robusta (gracias a Justin McCandless, ahora también admite referencias 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
fuente
30

Lo siguiente crea dos instancias del mismo objeto. Lo encontré y lo estoy usando actualmente. Es simple y fácil de usar.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
nathan rogers
fuente
¿Hay algo malo con esta respuesta? Es más útil como solución independiente, pero simple; Pero la solución jQuery es más popular. ¿Porqué es eso?
ceremcem
Sí, por favor házmelo saber. Parece estar funcionando según lo previsto, si hay alguna rotura oculta en algún lugar, necesito usar una solución diferente.
Nathan Rogers
44
Para un objeto simple, esto es aproximadamente 6 veces más lento en Chrome que la respuesta dada, y se vuelve mucho más lento a medida que crece la complejidad del objeto. Escala terriblemente y puede obstruir su aplicación muy rápidamente.
tic
1
No necesita datos, solo una comprensión de lo que está sucediendo. Esta técnica de clonación serializa todo el objeto en una cadena, luego analiza esa serialización de cadena para construir un objeto. Inherentemente, eso será mucho más lento que simplemente reorganizar algo de memoria (que es lo que hacen los clones más sofisticados). Pero dicho esto, para proyectos pequeños a medianos (dependiendo de su definición de "mediano") ¿a quién le importa si es incluso 1000 veces menos eficiente? Si sus objetos son pequeños y no los clona una tonelada, 1000x de prácticamente nada sigue siendo prácticamente nada.
machineghost
3
Además, este método pierde métodos (o cualquier cosa que no esté permitida en JSON), además: JSON.stringify convertirá los objetos Date en cadenas, ... y no al revés;) Manténgase alejado de esta solución.
Sr. MT
22

Crockford sugiere (y prefiero) usar esta función:

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

var newObject = object(oldObject);

Es conciso, funciona como se esperaba y no necesita una biblioteca.


EDITAR:

Este es un polyfill para Object.create, así que también puedes usarlo.

var newObject = Object.create(oldObject);

NOTA: Si usa algo de esto, puede tener problemas con alguna iteración de quién lo usa hasOwnProperty. Porque, createcrea un nuevo objeto vacío que hereda oldObject. Pero sigue siendo útil y práctico para clonar objetos.

Por ejemplo si oldObject.a = 5;

newObject.a; // is 5

pero:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
protonfish
fuente
99
corrígeme si me equivoco, pero ¿no es esa la función engendradora de Crockford para la herencia de prototipos? ¿Cómo se aplica al clon?
Alex Nolasco
3
Sí, tenía miedo de esta discusión: ¿Cuál es la diferencia práctica entre clonar, copiar y la herencia de prototipos, cuándo debería usar cada uno y qué funciones en esta página realmente están haciendo qué? Encontré esta página SO buscando en Google "objeto de copia de JavaScript". Lo que realmente estaba buscando era la función anterior, así que volví a compartir. Supongo que el autor de la pregunta también estaba buscando esto.
Chris Broski el
51
La diferencia entre clonar / copiar y herencia es que, usando su ejemplo, cuando cambio una propiedad de oldObject, la propiedad también cambia en newObject. Si hace una copia, puede hacer lo que quiera con oldObject sin cambiar newObject.
Ridcully
13
Esto romperá la comprobación de hasOwnProperty, por lo que es una forma bastante hacky de clonar un objeto y le dará 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

Lodash tiene un buen método _.cloneDeep (value) :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
opensas
fuente
55
Recomiendo eliminar esta y todas las otras respuestas que son solo referencias de una línea al .clone(...)método de una biblioteca de utilidad . Todas las bibliotecas principales las tendrán, y las breves y repetidas respuestas no detalladas no son útiles para la mayoría de los visitantes, que no utilizarán esa biblioteca en particular.
Jeremy Banks el
Una forma más fácil es usar _.merge({}, objA). Si solo lodash no mutara objetos en primer lugar, entonces la clonefunción no sería necesaria.
Rebs
77
Las búsquedas de Google para clonar objetos JS se refieren aquí. Estoy usando Lodash, así que esta respuesta es relevante para mí. No vayamos todos "delecionistas de wikipedia" en las respuestas por favor.
Rebs
2
En el Nodo 9, JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) es mucho más 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
fuente
17
El problema con el método es que si tiene subobjetos dentro del obj, sus referencias serán clonadas y no los valores de cada subobjeto.
Kamarey
1
solo haga que sea recursivo para que los subobjetos se clonen profundamente.
fiatjaf
simplemente curioso ... ¿no será la variable clon los punteros a las propiedades del objeto original? porque parece que no hay una nueva asignación de memoria
Rupesh Patel
3
Si. Esta es solo una copia superficial, por lo que el clon apuntará exactamente a los mismos objetos señalados por el objeto original.
Mark Cidade
Esta no es una respuesta. Literalmente, simplemente está rellenando un objeto con referencias a otro objeto. Realizar cambios en el objeto de origen hará cambios en el "clon".
Shawn Whinnery
20

Shallow copy one-liner ( 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

Y copia superficial de una sola línea ( ECMAScript 6ta edición , 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
fuente
66
Esto puede estar bien para objetos simples, pero solo copia los valores de propiedad. No toca la cadena del prototipo y al usarla Object.keysomite propiedades no enumerables y heredadas. Además, pierde los descriptores de propiedad al realizar una asignación directa.
Matt Bierner el
Si también copia el prototipo, le faltarían descriptores de propiedad y no enumerables, ¿sí? Bastante bueno. :)
sam
Dejando de lado el rendimiento, esta es una forma realmente conveniente de copiar un objeto superficialmente. A menudo uso esto para clasificar propiedades de descanso falsas en una tarea de desestructuración en mis componentes React.
mjohnsonengr
17

Solo porque no vi a AngularJS mencionado y pensé que la gente querría saber ...

angular.copy También proporciona un método de copia profunda de objetos y matrices.

Dan Atkinson
fuente
o podría usarse de la misma manera que jQiery extend:angular.extend({},obj);
Galvani
2
@Galvani: Debe tenerse en cuenta que jQuery.extendy angular.extendson las dos copias de poca profundidad. angular.copyEs una copia profunda.
Dan Atkinson el
16

Parece que todavía no existe un operador de clonación profunda ideal para objetos con forma de matriz. Como ilustra el siguiente código, el clonador jQuery de John Resig convierte matrices con propiedades no numéricas en objetos que no son matrices, y el clonador JSON de RegDwight descarta las propiedades no numéricas. Las siguientes pruebas ilustran estos puntos en varios 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 de página
fuente
14
como otros han señalado en los comentarios a la respuesta de Resig, si desea clonar un objeto tipo matriz, cambie {} a [] en la llamada extendida, por ejemplo, jQuery.extend (true, [], obj)
Anentropic
15

Tengo dos buenas respuestas dependiendo de si su objetivo es clonar un "objeto JavaScript simple" o no.

Supongamos también que su intención es crear un clon completo sin referencias prototipo al objeto de origen. Si no está interesado en un clon completo, puede usar muchas de las rutinas Object.clone () proporcionadas en algunas de las otras respuestas (patrón de Crockford).

Para los objetos JavaScript antiguos y simples, una manera buena y probada de clonar un objeto en tiempos de ejecución modernos es simplemente:

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

Tenga en cuenta que el objeto fuente debe ser un objeto JSON puro. Es decir, todas sus propiedades anidadas deben ser escalares (como boolean, string, array, object, etc.). Las funciones u objetos especiales como RegExp o Date no serán clonados.

¿Es eficiente? Diablos si. Hemos probado todo tipo de métodos de clonación y esto funciona mejor. Estoy seguro de que algunos ninjas podrían evocar un método más rápido. Pero sospecho que estamos hablando de ganancias marginales.

Este enfoque es simple y fácil de implementar. Envuélvalo en una función conveniente y si realmente necesita obtener algo de ganancia, continúe más adelante.

Ahora, para objetos JavaScript no simples, no hay una respuesta realmente simple. De hecho, no puede ser debido a la naturaleza dinámica de las funciones de JavaScript y el estado interno del objeto. La clonación profunda de una estructura JSON con funciones internas requiere que vuelva a crear esas funciones y su contexto interno. Y JavaScript simplemente no tiene una forma estandarizada de hacerlo.

La forma correcta de hacer esto, una vez más, es a través de un método conveniente que declare y reutilice dentro de su código. El método de conveniencia puede dotarse de cierta comprensión de sus propios objetos para que pueda asegurarse de recrear correctamente el gráfico dentro del nuevo objeto.

Estamos escritos por nuestra cuenta, pero el mejor enfoque general que he visto está cubierto aquí:

http://davidwalsh.name/javascript-clone

Esta es la idea correcta. El autor (David Walsh) ha comentado la clonación de funciones generalizadas. Esto es algo que puede elegir hacer, dependiendo de su caso de uso.

La idea principal es que necesita manejar especialmente la creación de instancias de sus funciones (o clases de prototipos, por así decirlo) por tipo. Aquí, ha proporcionado algunos ejemplos para RegExp y Date.

Este código no solo es breve, sino que también es muy legible. Es bastante fácil de extender.

¿Es esto eficiente? Diablos si. Dado que el objetivo es producir un verdadero clon de copia profunda, tendrá que recorrer los miembros del gráfico del objeto fuente. Con este enfoque, puede ajustar exactamente qué miembros secundarios tratar y cómo manejar manualmente los tipos personalizados.

Ahí vas. Dos enfoques. Ambos son eficientes en mi opinión.

Michael Uzquiano
fuente
13

Esta no es generalmente la solución más eficiente, pero hace lo que necesito. Casos de prueba simples a continuación ...

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

Prueba 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

Prueba de funcionamiento...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
Neatonk
fuente
11

AngularJS

Bueno, si estás usando angular, podrías hacer esto también

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

No estoy de acuerdo con la respuesta con los mejores votos aquí . Un clon profundo recursivo es mucho más rápido que el enfoque JSON.parse (JSON.stringify (obj)) mencionado.

Y aquí está la función para referencia 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
}
martillo programador
fuente
2
Me gustó este enfoque pero no maneja las fechas correctamente; considere agregar algo como if(o instanceof Date) return new Date(o.valueOf());después de verificar nulo `
Luis
Se bloquea en referencias circulares.
Harry
En el último Firefox estable, esto es mucho más largo que las otras estrategias en ese enlace Jsben.ch, en un orden de magnitud o más. Golpea a los demás en la dirección equivocada.
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
fuente
10

Solo cuando puede usar ECMAScript 6 o transpilers .

caracteristicas:

  • No activará getter / setter durante la copia.
  • Conserva el captador / colocador.
  • Conserva la información del prototipo.
  • Funciona con estilos de escritura OO tanto literales como funcionales .

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
fuente
9

Aquí hay un método completo de clone () que puede clonar cualquier objeto JavaScript. Maneja casi todos los 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;
};
usuario1547016
fuente
Convierte primitivas en objetos envolventes, no es una buena solución en la mayoría de los casos.
Danubian Sailor
@DanubianSailor: no creo que lo haga ... parece que devuelve primitivas desde el principio, y no parece estar haciéndoles nada que los convierta en objetos envolventes a medida que se devuelven.
Jimbo Jonny