¿Cómo clono correctamente un objeto JavaScript?

3376

Tengo un objeto x. Me gustaría copiarlo como objeto y, de modo que los cambios yno se modifiquen x. Me di cuenta de que copiar objetos derivados de objetos JavaScript integrados dará como resultado propiedades adicionales no deseadas. Esto no es un problema, ya que estoy copiando uno de mis propios objetos construidos literalmente.

¿Cómo clono correctamente un objeto JavaScript?

25
  • 31
    Vea esta pregunta: stackoverflow.com/questions/122102/…Niyaz 21/06/11 a las 10:13
  • 276
    Para JSON, usomObj=JSON.parse(JSON.stringify(jsonObject));Lord Loh. 2 feb 2013 a las 10:09
  • 72
    Realmente no entiendo por qué nadie sugiere Object.create(o), ¿hace todo lo que pide el autor? froginvasion 8 de agosto de 2014 a las 15:23
  • 51
    var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; Después de hacer esto, y.deep.keytambién será 2, por lo tanto, Object.create NO SE PUEDE UTILIZAR para la clonación ...Ruben Stolk 4 de julio de 2015 a las 15:04
  • 19
    @ r3wt que no funcionará ... Por favor publique solo después de hacer una prueba básica de la solución ..user3275211 16 de febrero de 2016 a las 18:54
1661

Hacer esto para cualquier objeto en JavaScript no será simple ni sencillo. Te encontrarás con el problema de elegir erróneamente atributos del prototipo del objeto que deberían dejarse en el prototipo y no copiarse en la nueva instancia. Si, por ejemplo, está agregando un clonemétodo a Object.prototype, como muestran algunas respuestas, deberá omitir explícitamente ese atributo. Pero Object.prototype, ¿qué sucede si se agregan otros métodos adicionales u otros prototipos intermedios que no conoce? En ese caso, copiará atributos que no debería, por lo que debe detectar atributos no locales imprevistos con el hasOwnPropertymétodo.

Además de los atributos no enumerables, encontrará un problema más difícil cuando intente copiar objetos que tienen propiedades ocultas. Por ejemplo, prototypees una propiedad oculta de una función. Además, se hace referencia al prototipo de un objeto con el atributo __proto__, que también está oculto, y no se copiará mediante un bucle for / in que se repita sobre los atributos del objeto de origen. Creo que __proto__podría ser específico del intérprete de JavaScript de Firefox y puede ser algo diferente en otros navegadores, pero te haces una idea. No todo es enumerable. Puede copiar un atributo oculto si conoce su nombre, pero no conozco ninguna forma de descubrirlo automáticamente.

Otro inconveniente más en la búsqueda de una solución elegante es el problema de configurar correctamente la herencia del prototipo. Si el prototipo de su objeto de origen es Object, entonces simplemente crear un nuevo objeto general con {}funcionará, pero si el prototipo de origen es un descendiente de Object, entonces se perderán los miembros adicionales de ese prototipo que omitió usando el hasOwnPropertyfiltro, o que estaban en el prototipo, pero no eran enumerables en primer lugar. Una solución podría ser llamar a la constructorpropiedad del objeto de origen para obtener el objeto de copia inicial y luego copiar los atributos, pero aún así no obtendrá atributos no enumerables. Por ejemplo, un Dateobjeto almacena sus datos como un miembro oculto:

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

var d1 = new Date();

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

La cadena de fecha para d1estará 5 segundos detrás de la de d2. Una forma de hacer que uno sea Dateigual a otro es llamando al setTimemétodo, pero eso es específico de la Dateclase. No creo que haya una solución general a prueba de balas para este problema, ¡aunque estaría feliz de estar equivocado!

Cuando tenía que poner en práctica profunda en general copiar terminé comprometer asumiendo que sólo tendría que copiar una llanura Object, Array, Date, String, Number, o Boolean. Los últimos 3 tipos son inmutables, por lo que podría realizar una copia superficial y no preocuparme de que cambie. Además asumí que cualquier elemento contenido en Objecto Arraytambién sería uno de los 6 tipos simples en esa lista. Esto se puede lograr con un código como el siguiente:

function clone(obj) {
    var copy;

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

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

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

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

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

La función anterior funcionará adecuadamente para los 6 tipos simples que mencioné, siempre que los datos de los objetos y las matrices formen una estructura de árbol. Es decir, no hay más de una referencia a los mismos datos en el objeto. Por ejemplo:

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

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

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

No podrá manejar ningún objeto JavaScript, pero puede ser suficiente para muchos propósitos siempre y cuando no asuma que simplemente funcionará para cualquier cosa que le arroje.

1
  • Faltan claves de símbolo y valores de símbolo. Hoy en día usar Object.getOwnPropertyDescriptorses mejor. Sebastian Simon 10 de julio a las 15:33
1065

Si no usa Dates, functions, undefined, regExp o Infinity dentro de su objeto, un trazador de líneas muy simple 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'
}
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()

Esto funciona para todo tipo de objetos que contienen objetos, matrices, cadenas, valores booleanos y números.

Consulte también este artículo sobre el algoritmo de clonación estructurada de navegadores que se utiliza al publicar mensajes desde y hacia un trabajador. También contiene una función para la clonación profunda.

0
800

Con jQuery, puede realizar copias superficiales con extender :

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

los cambios posteriores al copiedObjectno afectarán al originalObject, y viceversa.

O para hacer una copia profunda :

var copiedObject = jQuery.extend(true, {}, originalObject)
2
  • 166
    o incluso:var copiedObject = jQuery.extend({},originalObject);Grant McLean 8 de mayo de 2011 a las 2:11
  • 84
    También es útil especificar true como el primer parámetro para una copia en profundidad:jQuery.extend(true, {}, originalObject);Will Shaver 21/06/11 a las 0:19
763

En ECMAScript 6 existe el método Object.assign , que copia los valores de todas las propiedades propias enumerables de un objeto a otro. Por ejemplo:

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

Pero tenga en cuenta que esta es una copia superficial : los objetos anidados todavía se copian como referencia.

0
287

Por MDN :

  • Si desea una copia superficial, use Object.assign({}, a)
  • Para una copia "profunda", utilice JSON.parse(JSON.stringify(a))

No se necesitan bibliotecas externas, pero primero debe verificar la compatibilidad del navegador .

0
141

Hay muchas respuestas, pero ninguna que mencione Object.create de ECMAScript 5, que ciertamente no le da una copia exacta, pero establece la fuente como el prototipo del nuevo objeto.

Por lo tanto, esta no es una respuesta exacta a la pregunta, pero es una solución de una línea y, por lo tanto, elegante. Y funciona mejor para 2 casos:

  1. Donde tal herencia es útil (¡duh!)
  2. Donde el objeto de origen no se modificará, por lo que la relación entre los 2 objetos no será un problema.

Ejemplo:

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

¿Por qué considero que esta solución es superior? Es nativo, por lo tanto, no hay bucles ni recursiones. Sin embargo, los navegadores más antiguos necesitarán un polyfill.

1
  • 109
    Esto es herencia prototípica, no clonación. Son cosas completamente diferentes. El nuevo objeto no tiene propiedades propias, solo apunta a las propiedades del prototipo. El objetivo de la clonación es crear un nuevo objeto nuevo que no haga referencia a ninguna propiedad en otro objeto. d13 16/01/2014 a las 16:18
138

Una forma elegante de clonar un objeto Javascript 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);

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.

Lee mas...

El polyfill para admitir navegadores más 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;
    }
  });
}
1
  • 51
    esto solo realizará una "clonación" superficialMarcus Junius Brutus 25/07/2016 a las 13:27
93

Hay varios problemas con la mayoría de las soluciones en Internet. Entonces decidí hacer un seguimiento, que incluye, por qué la respuesta aceptada no debería ser aceptada.

situación inicial

Quiero hacer una copia profunda de un Javascript Objectcon todos sus hijos y sus hijos, etc. Pero ya que no soy la clase de un desarrollador normal, mi Objecttiene lo normal properties , circular structurese incluso nested objects.

Así que creemos una circular structurey una nested objectprimera.

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

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

Juntemos todo en un Objectnombre a.

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

A continuación, queremos copiar aen una variable nombrada by mutarla.

var b = a;

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

Sabes lo que pasó aquí porque si no, ni siquiera aterrizarías en esta gran pregunta.

console.log(a, b);

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

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

Ahora busquemos una solución.

JSON

El primer intento que intenté fue usar JSON.

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

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

No pierdas demasiado tiempo en eso, lo conseguirás TypeError: Converting circular structure to JSON.

Copia recursiva (la "respuesta" aceptada)

Echemos un vistazo a la respuesta aceptada.

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

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

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

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

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

Se ve bien, ¿eh? Es una copia recursiva del objeto y también maneja otros tipos, como Date, pero eso no era un requisito.

var b = cloneSO(a);

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

Recurrencia y circular structuresno funciona bien juntos ...RangeError: Maximum call stack size exceeded

solución nativa

Después de discutir con mi compañero de trabajo, mi jefe nos preguntó qué sucedió y encontró una solución simple después de buscar en Google. Se llama Object.create.

var b = Object.create(a);

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

Esta solución se agregó a Javascript hace algún tiempo e incluso maneja circular structure.

console.log(a, b);

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

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

... y ya ves, no funcionó con la estructura anidada en el interior.

polyfill para la solución nativa

Hay un polyfill para Object.createen el navegador más antiguo al igual que el IE 8. Es algo parecido a lo recomendado por Mozilla y, por supuesto, no es perfecto y da como resultado el mismo problema que la solución nativa .

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

var b = clonePF(a);

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

Lo he puesto Ffuera del alcance para que podamos echar un vistazo a lo que instanceofnos dice.

console.log(a, b);

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

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

console.log(typeof a, typeof b);

a --> object
b --> object

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

a --> true
b --> true

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

a --> false
b --> true

El mismo problema que la solución nativa , pero un resultado un poco peor.

la mejor (pero no perfecta) solución

Al indagar, encontré una pregunta similar ( en Javascript, al realizar una copia profunda, ¿cómo evito un ciclo, debido a que una propiedad es "esto"? ) A esta, pero con una solución mucho mejor.

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

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

var b = cloneDR(a);

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

Y echemos un vistazo a la salida ...

console.log(a, b);

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

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

console.log(typeof a, typeof b);

a --> object
b --> object

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

a --> true
b --> true

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

a --> false
b --> false

Los requisitos coinciden, pero todavía hay algunos problemas menores, incluido el cambio instancede de nestedy circa Object.

The structure of trees that share a leaf won't be copied, they will become two independent leaves:

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

conclusión

La última solución que usa recursividad y un caché puede no ser la mejor, pero es una copia en profundidad real del objeto. Maneja sencilla properties, circular structuresy nested object, pero se hace un lío la instancia de ellos durante la clonación.

jsfiddle

6
  • 12
    entonces la conmoción es evitar ese problema :)mikus 23/10/2014 a las 11:39
  • @mikus hasta que haya una especificación real que cubra más que solo los casos de uso básicos, sí. Fabio Poloni 23/10/2014 a las 14:41
  • 2
    Un buen análisis de las soluciones proporcionadas anteriormente, pero la conclusión extraída por el autor indica que no hay solución a esta pregunta. Amir Mog 16 de agosto de 2016 a las 16:53
  • 2
    Es una pena que JS no incluya la función de clonación nativa. l00k 14/11/2016 a las 14:53
  • 1
    Entre todas las respuestas principales, creo que esta está cerca de la correcta. KTU 15 de mayo de 2017 a las 4:24
80

Si está de acuerdo con una copia superficial, la biblioteca underscore.js tiene un método de clonación .

y = _.clone(x);

o puedes extenderlo como

copiedObject = _.extend({},originalObject);
2
  • 2
    Gracias. Usando esta técnica en un servidor Meteor. Turbo 1 de abril de 2015 a las 4:30
  • Para comenzar rápidamente con lodash, recomiendo aprender npm, Browserify y lodash. Obtuve clone para trabajar con 'npm i --save lodash.clone' y luego 'var clone = require (' lodash.clone ');' Para que Require funcione, necesitas algo como browserify. Una vez que lo instale y aprenda cómo funciona, usará 'browserify yourfile.js> bundle.js; start chrome index.html' cada vez que ejecute su código (en lugar de ir directamente a Chrome). Esto recopila su archivo y todos los archivos que requirió del módulo npm en bundle.js. Sin embargo, probablemente puedas ahorrar tiempo y automatizar este paso con Gulp. user11104582 9/04/19 a las 19:43
73

Bien, imagina que tienes este objeto a continuación y quieres clonarlo:

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

o

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

la respuesta depende principalmente de qué ECMAscript esté usando, en ES6+, simplemente puede usar Object.assignpara hacer el clon:

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

o usando un operador de propagación como este:

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

Pero si usa ES5, puede usar algunos métodos, pero JSON.stringifysolo asegúrese de no usar una gran cantidad de datos para copiar, pero podría ser una línea útil en muchos casos, algo como esto:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over
4
  • ¿Puede dar un ejemplo de lo big chunk of dataque equivaldría a? 100kb? 100 MB? ¡Gracias! user1063287 14 de septiembre de 2018 a las 11:04
  • Sí, @ user1063287, básicamente los datos más grandes, el rendimiento es peor ... así que realmente depende, no un kb, mb o gb, se trata más de cuántas veces quieres hacer eso también ... Además, no funcionará para funciones y otros productos ...Alireza 14/03/19 a las 9:58
  • 3
    Object.assignhace una copia superficial (al igual que la propagación, @Alizera)Bogdan D 29 de mayo de 2019 a las 13:42
  • No puedes usar let in es5: ^) @AlirezaWomble 1 de mayo de 2020 a las 14:56
49

Actualización 06 julio 2020

Hay tres (3) formas de clonar objetos en JavaScript. Como los objetos en JavaScript son valores de referencia, no puede simplemente copiar usando el =.

Las formas son:

const food = { food: 'apple', drink: 'milk' }


// 1. Using the "Spread"
// ------------------

{ ...food }


// 2. Using "Object.assign"
// ------------------

Object.assign({}, food)


// 3. "JSON"
// ------------------

JSON.parse(JSON.stringify(food))

// RESULT:
// { food: 'apple', drink: 'milk' }

Espero que esto se pueda utilizar como resumen de referencia.

6
  • ¿Y esto agrega qué información nueva / única a esta pregunta? Andreas 6 de julio de 2020 a las 8:24
  • 9
    El JSONenfoque eliminaría cualquier método del objetoAndreas 6 de julio de 2020 a las 8:25
  • 1
    Crear una cadena a partir de un objeto y luego analizar esa cadena en otro objeto solo para copiar el objeto es una especie de estilo de programación de Monty Python :-DJaromír Adamec 10 de ene a las 14:24
  • 1
    Esto solo funciona para objetos literales y objetos que se pueden representar como tales, pero no para "objetos" genéricos como los que se encuentran en los lenguajes OO. Esto parece ser lo que pidió el OP, por lo que está bien, pero no es una solución universal para todo tipo de objeto. bart 31 de enero a las 12:34
  • El operador de propagación y Object.assign fallan para los objetos con una jerarquía, es decir. objetos anidados. JSON.parse / stringify funciona, pero como se mencionó no copia métodos. iCode 31 mar a las 17:40
47

Una solución particularmente poco elegante es usar la codificación JSON para hacer copias profundas de objetos que no tienen métodos miembros. La metodología es codificar JSON su objeto de destino, luego, al decodificarlo, obtiene la copia que está buscando. Puedes decodificar tantas veces como quieras para hacer tantas copias como necesites.

Por supuesto, las funciones no pertenecen a JSON, por lo que esto solo funciona para objetos sin métodos miembros.

Esta metodología fue perfecta para mi caso de uso, ya que estoy almacenando blobs JSON en un almacén de valores clave, y cuando se exponen como objetos en una API de JavaScript, cada objeto en realidad contiene una copia del estado original del objeto, por lo que puede calcular el delta después de que la persona que llama haya mutado el objeto expuesto.

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

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

object2.key = "a change";
console.log(object1);// returns value
4
  • ¿Por qué las funciones no pertenecen a JSON? Los he visto transferidos como JSON más de una vez ...the_drow 29/10/09 a las 20:37
  • 5
    Las funciones no forman parte de la especificación JSON porque no son una forma segura (o inteligente) de transferir datos, que es para lo que se creó JSON. Sé que el codificador JSON nativo en Firefox simplemente ignora las funciones que se le pasan, pero no estoy seguro del comportamiento de los demás. Kris Walker 30/10/09 a las 10:27
  • 1
    @mark: { 'foo': function() { return 1; } }es un objeto construido literalmente. abarnert 14 de agosto de 2012 a las 1:58
  • Las funciones de @abarnert no son datos. "Literales de función" es un nombre inapropiado, ya que las funciones pueden contener código arbitrario, incluidas asignaciones y todo tipo de cosas "no serializables". deprecated 30/04/13 a las 10:38
39

Simplemente puede usar una propiedad de extensión para copiar un objeto sin referencias. Pero tenga cuidado (ver comentarios), la 'copia' está solo en el nivel más bajo de objeto / matriz. ¡Las propiedades anidadas siguen siendo referencias!


Clon completo:

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

// => mutate without references:

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

Clonar con referencias en segundo nivel:

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

// => nested object is still a references:

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

JavaScript en realidad no admite clones profundos de forma nativa. Utilice una función de utilidad. Por ejemplo Ramda:

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

3
  • 1
    Esto no funciona ... probablemente funcionaría cuando x será una matriz, por ejemplo, x = ['ab', 'cd', ...]Kamil Kiełczewski 14 de abril de 2016 a las 8:23
  • 3
    Esto funciona, pero tenga en cuenta que es una copia PROFUNDA, por lo tanto, cualquier referencia profunda a otros objetos sigue siendo una referencia. Bugs Bunny 17 de mayo de 2016 a las 13:01
  • Un clon parcial también puede ocurrir de esta manera:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}Cristian Traìna 13/08/18 a las 17:37
29

Para aquellos que usan AngularJS, también existe un método directo para clonar o extender los objetos en esta biblioteca.

var destination = angular.copy(source);

o

angular.copy(source, destination);

Más en la documentación de angular.copy ...

1
  • 3
    Esta es una copia profunda FYI. zamnuts 19 de septiembre de 2014 a las 10:27
27

De este artículo: Cómo copiar matrices y objetos en Javascript por Brian Huisman:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};
6
  • 4
    Esto está cerca, pero no funciona para ningún objeto. Intente clonar un objeto Date con esto. No todas las propiedades son enumerables, por lo que no todas aparecerán en el ciclo for / in. A. Levy 8 de abril de 2009 a las 4:17
  • Agregar al prototipo de objeto como este rompió jQuery para mí. Incluso cuando cambié el nombre a clone2. iPadDeveloper2011 27/08/12 a las 23:19
  • 3
    @ iPadDeveloper2011 El código anterior tenía un error en el que creaba una variable global llamada 'i' '(para i en esto)', en lugar de '(para var i en esto)'. Tengo suficiente karma para editarlo y arreglarlo, así que lo hice. mikemaccana 22/09/12 a las 22:09
  • 2
    @Calvin: esto debe crearse como una propiedad no enumerable, de lo contrario aparecerá 'clon' en los bucles 'for'. mikemaccana 1/10/12 a las 10:54
  • 3
    ¿Por qué no es var copiedObj = Object.create(obj);una excelente manera también? Dan P. 12/04/2014 a las 20:16
26

La respuesta de Levy está casi completa, aquí está mi pequeña contribución: hay una manera de manejar las referencias recursivas , vea esta línea

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

Si el objeto es un elemento DOM XML, debemos usar cloneNode en su lugar

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

Inspirado por el estudio exhaustivo de A. Levy y el enfoque de creación de prototipos de Calvin, ofrezco esta solución:

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

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

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

Vea también la nota de Andy Burke en las respuestas.

1
  • 3
    Date.prototype.clone = function() {return new Date(+this)};RobG 2 de diciembre de 2014 a las 12:20
25

Aquí hay una función que puede utilizar.

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}
5
  • 10
    Esta respuesta está bastante cerca, pero no del todo correcta. Si intenta clonar un objeto Date, no obtendrá la misma fecha porque la llamada a la función constructora Date inicializa la nueva Fecha con la fecha / hora actual. Ese valor no es enumerable y el bucle for / in no lo copiará. A. Levy 8 de abril de 2009 a las 4:21
  • No es perfecto, pero es bueno para esos casos básicos. Por ejemplo, permitir la clonación simple de un argumento que puede ser un objeto básico, matriz o cadena. james_womack 1 de nov. De 2013 a las 20:36
  • Votó a favor por llamar correctamente al constructor usando new. La respuesta aceptada no lo hace. GetFree 14 de junio de 2015 a las 6:29
  • funciona en el nodo todo lo demás! todavía quedan enlaces de referenciauser956584 15/07/2017 a las 22:38
  • El pensamiento recursivo es genial, pero si el valor es una matriz, ¿funcionará? Q10Viking 30/12/19 a las 11:32
25
let objClone = { ...obj };

Tenga en cuenta que los objetos anidados todavía se copian como referencia.

4
  • 1
    ¡Gracias por la sugerencia de que los objetos anidados todavía se copian como referencia! Casi me vuelvo loco al depurar mi código porque modifiqué las propiedades anidadas en el "clon" pero el original se modificó. Benny Neugebauer 2 de enero de 2019 a las 23:32
  • Esto es ES2016, no 2018, y esta respuesta se dio dos años antes . Dan Dascalescu 13/06/19 a las 23:37
  • Entonces, ¿qué pasa si también quiero una copia de la propiedad anidadaSunil Garg 4 de septiembre de 2019 a las 8:12
  • 1
    @SunilGarg Para copiar la propiedad anidada también puede usar const objDeepClone = JSON.parse(JSON.stringify(obj));Pavan Garre 6 de septiembre de 2019 a las 13:45
22

Usando Lodash:

var y = _.clone(x, true);
2
  • 6
    Dios mío, sería una locura reinventar la clonación. Ésta es la única respuesta sensata. Dan Ross 11 de septiembre de 2013 a las 8:48
  • 7
    Prefiero _.cloneDeep(x)ya que esencialmente es lo mismo que el anterior, pero se lee mejor. garbanzio 18 dic '14 a las 19:40
22

En ES-6, simplemente puede usar Object.assign (...). Ex:

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

Una buena referencia está aquí: https://googlechrome.github.io/samples/object-assign-es6/

6
  • 12
    No clona profundamente el objeto. August 1 de junio de 2017 a las 5:31
  • Esa es una tarea, no una copia. clone.Title = "solo un clon" significa que obj.Title = "solo un clon". HoldOffHunger 16 de agosto de 2017 a las 16:05
  • @HoldOffHunger Estás equivocado. Compruébelo en la consola JS de su navegador ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)collapsar 1 de septiembre de 2017 a las 11:00
  • @collapsar: Eso es precisamente lo que verifiqué, entonces console.log (persona) será "Whazzup", no "Thor Odinson". Vea el comentario de August. HoldOffHunger 1 de septiembre de 2017 a las 11:40
  • 1
    @HoldOffHunger no ocurre en Chrome 60.0.3112.113 ni en Edge 14.14393; El comentario de August no se aplica ya que los valores de los tipos primitivos de objlas propiedades de 'son de hecho clonados. Los valores de propiedad que son Objetos en sí mismos no se clonarán. collapsar 1 de septiembre de 2017 a las 12:50
22

Rendimiento

Hoy 2020.04.30 realizo pruebas de las soluciones elegidas en Chrome v81.0, Safari v13.1 y Firefox v75.0 en MacOs High Sierra v10.13.6.

Me concentro en la velocidad de copia de DATOS (objeto con campos de tipo simple, no métodos, etc.). Las soluciones AI solo pueden hacer copias superficiales, las soluciones JU puede hacer copias profundas.

Resultados para copia superficial

  • La solución {...obj}(A) es más rápida en Chrome y Firefox y media rápida en Safari.
  • La solución basada en Object.assign(B) es rápida en todos los navegadores.
  • Las soluciones jQuery (E) y lodash (F, G, H) son medias / bastante rápidas
  • la solución JSON.parse/stringify(K) es bastante lenta
  • las soluciones D y U son lentas en todos los navegadores

ingrese la descripción de la imagen aquí

Resultados para copia profunda

  • La solución Q es la más rápida en todos los navegadores.
  • jQuery (L) y lodash (J) son medio rápido
  • la solución JSON.parse/stringify(K) es bastante lenta
  • La solución U es la más lenta en todos los navegadores.
  • lodash (J) y la solución U se bloquean en Chrome para un objeto profundo de 1000 niveles

ingrese la descripción de la imagen aquí

Detalles

Para las soluciones elegidas: A B C (my) D E F G H I J K L M N O P Q R S T U , realizo 4 pruebas

  • shallow-small: objeto con 10 campos no anidados; puede ejecutarlo AQUÍ
  • shallow-big: objeto con 1000 campos no anidados; puede ejecutarlo AQUÍ
  • deep-small: objeto con 10 campos anidados en niveles - puede ejecutarlo AQUÍ
  • deep-big: objeto con 1000 campos anidados en niveles - puede ejecutarlo AQUÍ

Los objetos utilizados en las pruebas se muestran en el siguiente fragmento

let obj_ShallowSmall = {
  field0: false,
  field1: true,
  field2: 1,
  field3: 0,
  field4: null,
  field5: [],
  field6: {},
  field7: "text7",
  field8: "text8",
}

let obj_DeepSmall = {
  level0: {
   level1: {
    level2: {
     level3: {
      level4: {
       level5: {
        level6: {
         level7: {
          level8: {
           level9: [[[[[[[[[['abc']]]]]]]]]],
  }}}}}}}}},
};

let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,{});


let obj_DeepBig = genDeepObject(1000);



// ------------------
// Show objects
// ------------------

console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall));
console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall));
console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig));
console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig));




// ------------------
// HELPERS
// ------------------

function getField(k) {
  let i=k%10;
  if(i==0) return false;
  if(i==1) return true;
  if(i==2) return k;
  if(i==3) return 0;
  if(i==4) return null;
  if(i==5) return [];
  if(i==6) return {};  
  if(i>=7) return "text"+k;
}

function genDeepObject(N) {
  // generate: {level0:{level1:{...levelN: {end:[[[...N-times...['abc']...]]] }}}...}}}
  let obj={};
  let o=obj;
  let arr = [];
  let a=arr;

  for(let i=0; i<N; i++) {
    o['level'+i]={};
    o=o['level'+i];
    let aa=[];
    a.push(aa);
    a=aa;
  }

  a[0]='abc';
  o['end']=arr;
  return obj;
}

El siguiente fragmento presenta soluciones probadas y muestra las diferencias entre ellas

function A(obj) {
  return {...obj}
}

function B(obj) {
  return Object.assign({}, obj); 
}

function C(obj) {
  return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {})
}

function D(obj) {
  let copyOfObject = {};
  Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));
  return copyOfObject;
}

function E(obj) {
  return jQuery.extend({}, obj) // shallow
}

function F(obj) {
  return _.clone(obj);
}

function G(obj) {
  return _.clone(obj,true);
}

function H(obj) {
  return _.extend({},obj);
}

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

function J(obj) {
  return _.cloneDeep(obj,true);
}

function K(obj) {
	return JSON.parse(JSON.stringify(obj));
}

function L(obj) {
  return jQuery.extend(true, {}, obj) // deep
}

function M(obj) {
  if(obj == null || typeof(obj) != 'object')
    return obj;    
  var temp = new obj.constructor(); 
  for(var key in obj)
    temp[key] = M(obj[key]);    
  return temp;
}

function N(obj) {
  let EClone = function(obj) {
    var newObj = (obj instanceof Array) ? [] : {};
    for (var i in obj) {
      if (i == 'EClone') continue;
      if (obj[i] && typeof obj[i] == "object") {
        newObj[i] = EClone(obj[i]);
      } else newObj[i] = obj[i]
    } return newObj;
  };

	return EClone(obj);
};

function O(obj) {
    if (obj == null || typeof obj != "object") return obj;
    if (obj.constructor != Object && obj.constructor != Array) return obj;
    if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
        obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
        return new obj.constructor(obj);

    let to = new obj.constructor();

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

    return to;
}

function P(obj) {
  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;
  }
  return clone({},obj);
}

function Q(obj) {
    var copy;

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

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

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

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

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

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

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

function S(obj) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(object) {
        if (typeof object !== 'object' ||
            object === null ||
            object instanceof HTMLElement
        )
            return object; // primitive value or HTMLElement

        if (object instanceof Date) 
            return new Date().setTime(object.getTime());

        if (object instanceof RegExp) 
            return new RegExp(object.source, object.flags);

        if (cache.has(object)) 
            return cache.get(object);

        const result = object instanceof Array ? [] : {};

        cache.set(object, result); // store reference to object before the recursive starts

        if (object instanceof Array) {
            for(const o of object) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(object); 

        for (const key of keys)
            result[key] = copy(object[key]);

        return result;
    }

    return copy(obj);
}

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

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

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

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

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

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

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

            clonedObjectsArray[objectId(obj)] = copy;

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

            return copy;
        }       


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



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

    return cloneObj;
}

function U(obj) {
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(obj)

  function defineProp(object, key, descriptor = {}, copyFrom = {}) {
    const { configurable: _configurable, writable: _writable }
      = Object.getOwnPropertyDescriptor(object, key)
      || { configurable: true, writable: true }

    const test = _configurable // Can redefine property
      && (_writable === undefined || _writable) // Can assign to property

    if (!test || arguments.length <= 2) return test

    const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
      || { configurable: true, writable: true } // Custom…
      || {}; // …or left to native default settings

    ["get", "set", "value", "writable", "enumerable", "configurable"]
      .forEach(attr =>
        descriptor[attr] === undefined &&
        (descriptor[attr] = basisDesc[attr])
      )

    const { get, set, value, writable, enumerable, configurable }
      = descriptor

    return Object.defineProperty(object, key, {
      enumerable, configurable, ...get || set
        ? { get, set } // Accessor descriptor
        : { value, writable } // Data descriptor
    })
  }

  function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        const fnStr = String(object)

        _object = new Function("return " +
          (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)
            ? "function " : ""
          ) + fnStr
        )()

        copyPropDescs(_object, object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                              // Stem from:
          case "[object Function]":       // `class`
          case "[object Undefined]":      // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:                        // `Proxy`
            _object = object
        }
    }

    return _object
  }


  function cloneObject(object) {
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key, { value: clone(object[key]) }, object)
    )

    return _object
  }


  function copyPropDescs(target, source) {
    Object.defineProperties(target,
      Object.getOwnPropertyDescriptors(source)
    )
  }
}
 
// ------------------------
// Test properties
// ------------------------


console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt`)

log(A);
log(B);
log(C);
log(D);
log(E);
log(F);
log(G);
log(H);
log(I);
log(J);
log(K);
log(L);
log(M);
log(N);
log(O);
log(P);
log(Q);
log(R);
log(S);
log(T);
log(U);

console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt
----
LEGEND:
shallow - solution create shallow copy
deep - solution create deep copy
func - solution copy functions
circ - solution can copy object with circular references
undefined - solution copy fields with undefined value
date - solution can copy date
RegExp - solution can copy fields with regular expressions
bigInt - solution can copy BigInt
`)


// ------------------------
// Helper functions
// ------------------------


function deepCompare(obj1,obj2) {
  return JSON.stringify(obj1)===JSON.stringify(obj2);
}

function getCase() { // pure data case
  return { 
    undef: undefined,
    bool: true, num: 1, str: "txt1",    
    e1: null, e2: [], e3: {}, e4: 0, e5: false,
    arr: [ false, 2, "txt3", null, [], {},
      [ true,4,"txt5",null, [], {},  [true,6,"txt7",null,[],{} ], 
        {bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
      ],
        {bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
    ], 
    obj: { 
        bool: true, num: 12, str: "txt13",
        e1: null, e2: [], e3: {}, e4: 0, e5: false,
        arr: [true,14,"txt15",null,[],{} ],
        obj: { 
          bool: true, num: 16, str: "txt17",
          e1: null, e2: [], e3: {}, e4: 0, e5: false,
          arr: [true,18,"txt19",null,[],{} ],
          obj: {bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
      } 
    } 
  };
}

function check(org, copy, field, newValue) {
  copy[field] = newValue;
  return deepCompare(org,copy); 
}

function testFunc(f) {
	let o = { a:1, fun: (i,j)=> i+j };
  let c = f(o);
  
  let val = false
  try{
    val = c.fun(3,4)==7;
  } catch(e) { }
  return val;
} 

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

  var o = {
      x: 'a',
      circ: new Circ(),
      obj_circ: null,
  };
  
  o.obj_circ = o;

  let val = false;

  try{
    let c = f(o);  
    val = (o.obj_circ == o) && (o.circ == o.circ.me);
  } catch(e) { }
  return val;
} 

function testRegExp(f) {
  let o = {
    re: /a[0-9]+/,
  };
  
  let val = false;

  try{
    let c = f(o);  
    val = (String(c.re) == String(/a[0-9]+/));
  } catch(e) { }
  return val;
}

function testDate(f) {
  let o = {
    date: new Date(),
  };
  
  let val = false;

  try{
    let c = f(o);  
    val = (+new Date(c.date) == +new Date(o.date));
  } catch(e) { }
  return val;
}

function testBigInt(f) {
  let val = false;
  
  try{
    let o = {
      big: 123n,
    };
  
    let c = f(o);  
  
    val = o.big == c.big;
  } catch(e) { }
  
  return val;
}

function log(f) {
  let o = getCase();  // orginal object
  let oB = getCase(); // "backup" used for shallow valid test
  
  let c1 = f(o); // copy 1 for reference
  let c2 = f(o); // copy 2 for test shallow values
  let c3 = f(o); // copy 3 for test deep values

  let is_proper_copy = deepCompare(c1,o);  // shoud be true
  
  // shallow changes
  let testShallow = 
    [ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',{}] ]
    .reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );
  
  // should be true (original object shoud not have changed shallow fields)
  let is_valid = deepCompare(o,oB); 

  // deep test (intruduce some change)
  if (c3.arr[6]) c3.arr[6][7].num = 777;
  
  let diff_shallow = !testShallow; // shoud be true (shallow field was copied)
  let diff_deep = !deepCompare(c1,c3);    // shoud be true (deep field was copied)
  let can_copy_functions = testFunc(f);
  let can_copy_circular = testCirc(f);
  let can_copy_regexp = testRegExp(f);
  let can_copy_date = testDate(f);
  let can_copy_bigInt = testBigInt(f);
  
  let has_undefined = 'undef' in c1; // field with undefined value is copied?  
  let is_ok = is_valid && is_proper_copy;
  let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string
  
  testFunc(f);
  
  if(is_ok) {
    console.log(`${f.name} ${b(diff_shallow)}   ${b(diff_deep)} ${b(can_copy_functions)} ${b(can_copy_circular)} ${b(has_undefined)}     ${b(can_copy_date)} ${b(can_copy_regexp)}  ${b(can_copy_bigInt)}`)
  } else {
    console.log(`${f.name}: INVALID ${is_valid} ${is_proper_copy}`,{c1})
  }
  
}
<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>

This snippet only presents tested solutions and show differences between them (but it no make performence tests)

A continuación, se muestran resultados de ejemplo para Chrome para objetos grandes y poco profundos

ingrese la descripción de la imagen aquí

0
20

Interesado en clonar objetos simples:

JSON.parse(JSON.stringify(json_original));

Fuente: ¿Cómo copiar un objeto JavaScript a una nueva variable NO por referencia?

3
  • Muy bonito, sencillo. Matt H 24 de mayo de 2019 a las 19:16
  • @MattH: esta respuesta ya se dio en 2012 . ¿lo viste? Mohammed, ¿comprobó las respuestas existentes antes de duplicar una de ellas? Dan Dascalescu 13/06/19 a las 23:40
  • bueno, esa es una forma. Ty nunca pensó en eso1-14x0r 8 abr.20 a las 21:53
16

Puede clonar un objeto y eliminar cualquier referencia del anterior utilizando una sola línea de código. Simplemente haz:

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

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

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

Para los navegadores / motores que actualmente no son compatibles con Object.create, puede usar este polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}
7
  • 1
    +1 Object.create(...)parece definitivamente el camino a seguir. René Nyffenegger 30/06/2014 a las 14:49
  • Respuesta perfecta. ¿Quizás podrías agregar una explicación para Object.hasOwnProperty? De esa manera, la gente sabrá cómo evitar buscar el enlace del prototipo. froginvasion 9 de agosto de 2014 a las 12:30
  • Funciona bien, pero ¿en qué navegadores funciona el polyfill? Ian Lunn 9/10/2014 a las 13:25
  • 11
    Esto está creando obj2 con un obj1 como prototipo. Solo funciona porque está sombreando al textmiembro en obj2. No está haciendo una copia, simplemente cediendo a la cadena de prototipos cuando no se encuentra un miembro en obj2. Nick Desaulniers 31/10/2014 a las 21:25
  • 2
    Esto NO lo crea "sin referencias", simplemente mueve la referencia al prototipo. Sigue siendo una referencia. Si una propiedad cambia en el original, también lo hará la propiedad del prototipo en el "clon". No es un clon en absoluto. Jimbo Jonny 9 de mayo de 2016 a las 0:01
15

¡Nueva respuesta a una vieja pregunta! Si tiene el placer de usar ECMAScript 2016 (ES6) con Spread Syntax , es fácil.

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

Esto proporciona un método limpio para una copia superficial de un objeto. Hacer una copia profunda, es decir, hacer una nueva copia de cada valor en cada objeto anidado de forma recursiva, requiere una de las soluciones más pesadas anteriores.

JavaScript sigue evolucionando.

4
  • 2
    no funciona cuando tienes funciones definidas en objetosPetr Marek 5 feb 2017 a las 22:06
  • Por lo que veo, el operador de propagación solo funciona con iterables - developer.mozilla.org dice: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterableOleh 4 de abril de 2017 a las 8:12
  • @Oleh así que usa `{... obj} en lugar de [... obj];`manikant gautam 5 de diciembre de 2017 a las 6:17
  • @manikantgautam Estaba usando Object.assign () antes, pero ahora la sintaxis de propagación de objetos es compatible con la última versión de Chrome, Firefox (todavía no en Edge y Safari). Su propuesta de ECMAScript ... pero Babel lo apoya hasta donde puedo ver, así que probablemente sea seguro de usar. Oleh 6 dic 2017 a las 15:11
15
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Solución ES6 si desea (superficial) clonar una instancia de clase y no solo un objeto de propiedad.

2
  • ¿En qué se diferencia esto de let cloned = Object.assign({}, obj)? ceztko 21 de mayo de 2019 a las 11:25
  • @ceztko Cuando objes una instancia de clase, Object.assign()no clona, ​​por ejemplo, métodos de clase (porque no son enumerables). flori 2 de enero a las 19:58
14

Para una copia profunda y clon, JSON.stringify y luego JSON.parse el objeto:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
1
  • bastante inteligente ... ¿alguna desventaja de este enfoque? Aleks 21/07/19 a las 1:56
13

Creo que hay una respuesta simple y funcional. En la copia profunda hay dos preocupaciones:

  1. Mantenga las propiedades independientes entre sí.
  2. Y mantenga vivos los métodos en el objeto clonado.

Entonces, creo que una solución simple será primero serializar y deserializar y luego hacer una asignación para copiar funciones también.

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

Aunque esta pregunta tiene muchas respuestas, espero que esta también ayude.

4
  • Aunque si se me permite importar lodash, prefiero usar lodash cloneDeep. ConductedClever 13/01/19 a las 6:01
  • 2
    Estoy usando JSON.parse (JSON.stringify (fuente)). Siempre trabajando. Misha 22 feb 2019 a las 15:50
  • 2
    @Misha, así te perderás las funciones. El término "obras" tiene muchos significados. ConductedClever 23/02/19 a las 5:26
  • Y tenga en cuenta que, de la forma que he mencionado, solo se copiarán las funciones de la primera capa. Entonces, si tenemos algunos objetos uno dentro del otro, entonces la única forma es copiar campo por campo de forma recursiva. ConductedClever 23 feb 2019 a las 5:29
11

Use lodash _.cloneDeep().

Copia superficial: lodash _.clone ()

Se puede hacer una copia superficial simplemente copiando la referencia.

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.clone(obj1);
obj1.a = 4;
obj1.b.c = 4;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
//{"a":4,"b":{"c":4,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
//{"a":0,"b":{"c":4,"e":{"f":100}}}

Copia superficial: lodash _.clone ()

Copia profunda: lodash _.cloneDeep ()

los campos están desreferenciados: en lugar de referencias a objetos que se copian

let obj1 = {
    a: 0,
    b: {
        c: 0,
        e: {
            f: 0
        }
    }
};
let obj3 = _.cloneDeep(obj1);
obj1.a = 100;
obj1.b.c = 100;
obj1.b.e.f = 100;

console.log(JSON.stringify(obj1));
{"a":100,"b":{"c":100,"e":{"f":100}}}

console.log(JSON.stringify(obj3));
{"a":0,"b":{"c":0,"e":{"f":0}}}

Copia profunda: lodash _.cloneDeep ()

11

(Lo siguiente fue principalmente una integración de @ Maciej Bukowski , @ A. Levy , @ Jan Turoň , las respuestas de @ Redu , y @ LeviRoberts , los comentarios de @ RobG , ¡muchas gracias a ellos!)

¿ Copia profunda ? - ¡SÍ! (principalmente); ¿
Copia superficial ? - ¡NO! (excepto Proxy).

Sinceramente, les doy la bienvenida a todos a probar clone().
Además, defineProp()está diseñado para (re) definir o copiar de forma fácil y rápida cualquier tipo de descriptor.

Función

function clone(object) {
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(object)


  function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        _object = copyFn(object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                                  // Stem from:
          case "[object Function]":
            switch (object[Symbol.toStringTag]) {
              case undefined:
                _object = cloneObject(object) // `class`
                break

              case "AsyncFunction":
              case "GeneratorFunction":
              case "AsyncGeneratorFunction":
                _object = copyFn(object)
                break

              default:
                _object = object
            }
            break

          case "[object Undefined]":          // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:
            _object = object                  // `Proxy`
        }
    }

    return _object
  }


  function cloneObject(object) {
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key, { value: clone(object[key]) }, object)
    )

    return _object
  }
}


function copyPropDescs(target, source) {
  Object.defineProperties(target,
    Object.getOwnPropertyDescriptors(source)
  )
}


function convertFnToStr(fn) {
  let fnStr = String(fn)
  if (fn.name.startsWith("[")) // isSymbolKey
    fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
  fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr)
    ? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
  return fnStr
}

function copyFn(fn) {
  const newFn = new Function(`return ${convertFnToStr(fn)}`)()
  copyPropDescs(newFn, fn)
  return newFn
}



function defineProp(object, key, descriptor = {}, copyFrom = {}) {
  const { configurable: _configurable, writable: _writable }
    = Object.getOwnPropertyDescriptor(object, key)
    || { configurable: true, writable: true }

  const test = _configurable // Can redefine property
    && (_writable === undefined || _writable) // Can assign to property

  if (!test || arguments.length <= 2) return test

  const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
    || { configurable: true, writable: true } // Custom…
    || {}; // …or left to native default settings

  ["get", "set", "value", "writable", "enumerable", "configurable"]
    .forEach(attr =>
      descriptor[attr] === undefined &&
      (descriptor[attr] = basisDesc[attr])
    )

  const { get, set, value, writable, enumerable, configurable }
    = descriptor

  return Object.defineProperty(object, key, {
    enumerable, configurable, ...get || set
      ? { get, set } // Accessor descriptor
      : { value, writable } // Data descriptor
  })
}

// Pruebas

const obj0 = {
  u: undefined,
  nul: null,
  t: true,
  num: 9,
  str: "",
  sym: Symbol("symbol"),
  [Symbol("e")]: Math.E,
  arr: [[0], [1, 2]],
  d: new Date(),
  re: /f/g,
  get g() { return 0 },
  o: {
    n: 0,
    o: { f: function (...args) { } }
  },
  f: {
    getAccessorStr(object) {
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    },
    f0: function f0() { },
    f1: function () { },
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params) { return param => param + params },
    f5: (a, b) => ({ c = 0 } = {}) => a + b + c
  }
}

defineProp(obj0, "s", { set(v) { this._s = v } })
defineProp(obj0.arr, "tint", { value: { is: "non-enumerable" } })
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", { set(v) { this._s = v + 1 } })
defineProp(obj1.re, "multiline", { value: true })

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - More kinds of functions")

const fnsForTest = {
  f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  async function() { },
  async asyncFunc() { },
  aFn: async function () { },
  *gen() { },
  async *asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  *[Symbol.iterator]() { yield* Object.keys(this) }
}

console.log(Reflect.ownKeys(fnsForTest).map(k =>
  `${String(k)}:
  ${fnsForTest[k].name}-->
    ${String(fnsForTest[k])}`
).join("\n"))

const normedFnsStr = `{
  f: function f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  function: async function() { },
  asyncFunc: async function asyncFunc() { },
  aFn: async function () { },
  gen: function* gen() { },
  asyncGen: async function* asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  [Symbol.iterator]: function* () { yield* Object.keys(this) }
}`

const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`(${normedFnsStr})`))
console.log("Comparison of fnsForTest and its clone:",
  Reflect.ownKeys(fnsForTest).map(k =>
    [k, fnsForTest[k] === copiedFnsForTest[k]]
  )
)

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0's recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person {
  constructor(name) {
    this.name = name
  }
}

class Boy extends Person { }
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

Referencias

  1. Object.create()| MDN
  2. Object.defineProperties()| MDN
  3. Enumerabilidad y titularidad de inmuebles | MDN
  4. TypeError: valor de objeto cíclico | MDN

Trucos de lenguaje utilizados

  1. Agregar condicionalmente prop al objeto
1
  • Dado que Symbol("a") === Symbol("a")es false, ¿no debería clone(Symbol("a"))usarse Symbol(object.description)para crear un nuevo símbolo? ¿O esto tendría un impacto demasiado extraño en símbolos conocidos? Sebastian Simon 10 de julio a las 15:39
8

Esta es una adaptación del código de A. Levy para manejar también la clonación de funciones y referencias múltiples / cíclicas; lo que esto significa es que si dos propiedades en el árbol que se clona son referencias del mismo objeto, el árbol de objetos clonados tendrá estas las propiedades apuntan a un mismo clon del objeto referenciado. Esto también resuelve el caso de las dependencias cíclicas que, si no se controlan, conducen a un bucle infinito. La complejidad del algoritmo es O (n)

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

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

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

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

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

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

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

            clonedObjectsArray[objectId(obj)] = copy;

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

            return copy;
        }       


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



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

    return cloneObj;
}

Algunas pruebas rápidas

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

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

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

objclone = clone(obj);

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

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

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
1
  • 1
    A septiembre de 2016, esta es la única solución correcta a la pregunta. DomQ 27 de septiembre de 2016 a las 20:18