¿Cómo clono correctamente un objeto JavaScript?

3080

Tengo un objeto x. Me gustaría copiarlo como objetoy , de modo que los cambios yno se modifiquen x. Me di cuenta de que copiar objetos derivados de objetos JavaScript incorporados 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?

sonido_tipo
fuente
31
Vea esta pregunta: stackoverflow.com/questions/122102/…
Niyaz
257
Para JSON, usomObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh.
68
Realmente no entiendo por qué nadie sugiere Object.create(o), ¿hace todo lo que el autor pregunta?
froginvasion
45
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 clonar ...
Ruben Stolk
18
@ r3wt eso no funcionará ... Publique solo después de hacer una prueba básica de la solución ..
akshay

Respuestas:

1562

Hacer esto para cualquier objeto en JavaScript no será simple o directo. Te encontrarás con el problema de recoger 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 para Object.prototype, como lo muestran algunas respuestas, deberá omitir explícitamente ese atributo. Pero Object.prototype, ¿qué sucede si hay otros métodos adicionales agregados u otros prototipos intermedios que desconoce? En ese caso, copiará los atributos que no debería, por lo que debe detectar atributos no locales imprevistos con elhasOwnProperty mé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, el prototipo de un objeto está referenciado con el atributo __proto__, que también está oculto, y no será copiado por un bucle for / in iterando sobre los atributos del objeto fuente. Creo que __proto__puede ser específico para el intérprete de JavaScript de Firefox y puede ser algo diferente en otros navegadores, pero se entiende la imagen. No todo es enumerable. Puede copiar un atributo oculto si conoce su nombre, pero no conozco ninguna forma de descubrirlo automáticamente.

Otro inconveniente en la búsqueda de una solución elegante es el problema de configurar la herencia del prototipo correctamente. Si el prototipo de su objeto fuente es Object, simplemente {}creará un nuevo objeto general con el que funcionará, pero si el prototipo de la fuente es un descendiente de Object, entonces le faltará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 d1estará 5 segundos por detrás de la de d2. Una forma de hacer que uno sea Dateigual a otro es llamar 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. Supuse además 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 en 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 solo funcionará para cualquier cosa que le arroje.

A. Levy
fuente
55
casi funcionó bien en un nodejs, solo tuve que cambiar la línea para (var i = 0, var len = obj.length; i <len; ++ i) {a for (var i = 0; i <obj.length; ++ i) {
Trindaz

55
Para futuros googlers: la misma copia profunda, pasando referencias recursivamente en lugar de usar declaraciones de 'retorno' en gist.github.com/2234277
Trindaz

8
¿Sería hoy JSON.parse(JSON.stringify([some object]),[some revirer function])una solución?
KooiInc

55
En el primer fragmento, ¿estás seguro de que no debería ser var cpy = new obj.constructor()?
cyon

8
La asignación de objetos no parece hacer una copia verdadera en Chrome, mantiene la referencia al objeto original - terminó usando JSON.stringify y JSON.parse para clonar - funcionó perfectamente
1owk3y

975

Si no usa Dates, funciones, undefined, regExp o Infinity dentro de su objeto, un revestimiento 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, booleanos y números.

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


43
Tenga en cuenta que esto solo se puede utilizar para realizar pruebas. En primer lugar, está lejos de ser óptimo en términos de tiempo y consumo de memoria. En segundo lugar, no todos los navegadores tienen estos métodos.
Nux

2
Siempre puede incluir JSON2.js o JSON3.js. Los necesitaría para su aplicación de todos modos. Pero sí estoy de acuerdo con que esta podría no ser la mejor solución, ya que JSON.stringify no incluye propiedades heredadas.
Tim Hong

79
@Nux, ¿por qué no es óptimo en términos de tiempo y memoria? MiJyn dice: "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, copia en profundidad. Pero dado que JSON se implementa en código nativo (en la mayoría de los navegadores), esto será considerablemente más rápido que usar 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-an-object/79) ". stackoverflow.com/questions/122102/…
BeauCielBleu

17
Solo quiero agregar una actualización para octubre de 2014. Chrome 37+ es más rápido con JSON.parse (JSON.stringify (oldObject)); El beneficio de usar esto es que es muy fácil para un motor de JavaScript ver y optimizar algo mejor si lo desea.
mirhagk

17
Actualización de 2016: Esto debería funcionar ahora en casi todos los navegadores que se utilizan ampliamente. (vea ¿Puedo usar ...? ) La pregunta principal ahora sería si es lo suficientemente eficiente.
James Foster,

784

Con jQuery, puede copiar poco con extend :

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

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

O para hacer una copia profunda :

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

164
o incluso:var copiedObject = jQuery.extend({},originalObject);
Grant McLean

82
También es útil para especificar true como primer parámetro de copia profunda:jQuery.extend(true, {}, originalObject);
Will Shaver

66
Sí, este enlace me pareció útil (la misma solución que Pascal) stackoverflow.com/questions/122102/…
Garry English

3
Solo una nota, esto no copia el protoconstructor del objeto original
Sam Jones

1
De acuerdo con stackoverflow.com/questions/184710/… , parece que "copia superficial" simplemente copia la referencia de OriginalObject, entonces ¿por qué aquí dice eso ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa...? Lamento estar realmente confundido.
Carr

687

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 los objetos anidados todavía se copian como referencia.


Sí, creo que ese Object.assignes el camino a seguir. También es fácil rellenarlo

22
También tenga en cuenta que esto copiará sobre "métodos" definidos a través de literales de objetos (ya que estos son enumerables) pero no métodos desafiados a través del mecanismo de "clase" (ya que estos no son enumerables).
Marcus Junius Brutus

16
Creo que debería mencionarse que IE no lo admite, excepto Edge. Algunas personas todavía usan esto.
Saulius

1
Esto es lo mismo que @EugeneTiurin su respuesta.
Marchitez

55
Hablando por experiencia aquí ... NO use esto. Los objetos están diseñados para ser jerárquicos y solo copiará el primer nivel, lo que le llevará a asignar todo el objeto que ha copiado. Confía en mí, puede ahorrarte días de rascarte la cabeza.
ow3n

234

Por MDN :

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

No hay necesidad de bibliotecas externas, pero primero debe verificar la compatibilidad del navegador .


8
JSON.parse (JSON.stringify (a)) se ve hermoso, pero antes de usarlo, recomiendo comparar el tiempo que toma la colección deseada. Dependiendo del tamaño del objeto, esta podría no ser la opción más rápida.
Edza

3
Me di cuenta de que el método JSON convierte los objetos de fecha en cadenas pero no vuelve a las fechas. Tiene que lidiar con la diversión de las zonas horarias en Javascript y corregir manualmente cualquier fecha. Pueden ser casos similares para otros tipos además de las fechas
Steve Seeger

para Date, usaría moment.js ya que tiene clonefuncionalidad. ver más aquí momentjs.com/docs/#/parsing/moment-clone
Tareq

Esto es bueno, pero tenga cuidado si el objeto tiene funciones en el interior
lesolorzanov

Otro problema con el análisis json son las referencias circulares. Si hay referencias circulares dentro del objeto, esto se romperá
philoj

134

Hay muchas respuestas, pero ninguna que menciona 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 fuente no se modificará, lo que hace que la relación entre los 2 objetos no sea 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, sin bucles ni recursiones. Sin embargo, los navegadores más antiguos necesitarán un polyfill.


Nota: Object.create no es una copia profunda (itpastorn no mencionó ninguna recursión). Prueba:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts

103
Esto es herencia prototípica, no clonación. Estas son cosas completamente diferentes. El nuevo objeto no tiene ninguna de sus propias propiedades, 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

77
Estoy de acuerdo contigo completamente. También estoy de acuerdo en que esto no es clonación como podría ser 'intencionado'. Pero vamos gente, adopte la naturaleza de JavaScript en lugar de tratar de encontrar soluciones oscuras que no estén estandarizadas. Claro, no te gustan los prototipos y todos son "bla" para ti, pero de hecho son muy útiles si sabes lo que estás haciendo.
froginvasion

44
@RobG: Este artículo explica la diferencia entre hacer referencia y clonar: en.wikipedia.org/wiki/Cloning_(programming) . Object.createseñala las propiedades del padre a través de referencias. Eso significa que si los valores de propiedad del padre cambian, el niño también cambiará. Esto tiene algunos efectos secundarios sorprendentes con matrices anidadas y objetos que podrían conducir a errores difíciles de encontrar en su código si no los conoce: jsbin.com/EKivInO/2 . Un objeto clonado es un objeto completamente nuevo e independiente que tiene las mismas propiedades y valores que el padre, pero no está conectado al padre.
d13

1
Esto confunde a las personas ... Object.create () se puede usar como un medio para la herencia, pero la clonación no está cerca de eso.
prajnavantha

129

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

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

Perdón por la pregunta tonta, pero ¿por qué Object.assigntoma dos parámetros cuando la valuefunción en el polyfill toma solo un parámetro?
Qwertie

@Qwertie ayer Todos los argumentos se repiten y se fusionan en un solo objeto, priorizando las propiedades del último argumento aprobado
Eugene Tiurin

Oh, ya veo, gracias (no estaba familiarizado con el argumentsobjeto antes). Tengo problemas para encontrarlo a Object()través de Google ... es un tipo de letra, ¿no?
Qwertie

45
esto solo realizará una "clonación" superficial
Marcus Junius Brutus

1
Esta respuesta es exactamente la misma que esta: stackoverflow.com/questions/122102/… Sé que es de la misma persona, pero debe hacer referencia en lugar de simplemente copiar la respuesta.
lesolorzanov

88

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

situación inicial

Quiero copiar en profundidad un Javascript Objectcon todos sus hijos y sus hijos, etc. Pero como no soy un desarrollador normal, mi Objecttiene normal properties , circular structurese inclusonested objects .

Así que creemos un circular structurey un nested objectprimero.

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

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

Vamos a juntar todo en un Objectnombre a.

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

A continuación, queremos copiar aen una variable llamada 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 encontremos 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 ello, obtendrá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';

Recursión 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é pasó 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 se 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 ves, no funcionaba con la estructura anidada adentro.

polyfill para la solución nativa

Hay un polyfill para Object.createel navegador anterior como el IE 8. Es algo como lo recomienda 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

Mismo problema que la solución nativa , pero un poco peor de salida.

la mejor (pero no perfecta) solución

Al investigar, 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 nestedy circhacia Object.

La estructura de los árboles que comparten una hoja no se copiará, se convertirán en dos hojas independientes:

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

conclusión

La última solución que utiliza recursividad y un caché, puede no ser la mejor, pero es una copia profunda 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


12
así que la discusión es evitar ese problema :)
mikus

@mikus hasta que haya una especificación real que cubra más que solo los casos de uso básicos, sí.
Fabio Poloni

2
Un análisis correcto de las soluciones proporcionadas anteriormente, pero la conclusión del autor indica que no hay solución para esta pregunta.
Amir Mog

2
Es una pena que JS no incluya la función de clonación nativa.
l00k

1
Entre todas las respuestas principales, creo que esta está cerca de la correcta.
KTU

78

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
Gracias. Usando esta técnica en un servidor Meteor.
Turbo

Para comenzar rápidamente con lodash, recomendaría aprender npm, Browserify y lodash. Obtuve clon para trabajar con 'npm i --save lodash.clone' y luego 'var clone = require (' lodash.clone ');' Para que requiera trabajar, necesita algo como browserify. Una vez que lo instale y aprenda cómo funciona, usará 'browserify yourfile.js> bundle.js; inicie chrome index.html' cada vez que ejecute su código (en lugar de ir directamente a Chrome). Esto reúne su archivo y todos los archivos que necesita del módulo npm en bundle.js. Sin embargo, probablemente pueda ahorrar tiempo y automatizar este paso con Gulp.
Aaron Bell

66

OK, 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, 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.stringifyasegú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

¿Puede dar un ejemplo de lo big chunk of dataque equivaldría? 100kb? 100MB? ¡Gracias!
user1063287

Sí, @ user1063287, que básicamente los datos más grandes, el rendimiento 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 otras cosas ...
Alireza

3
Object.assignhace una copia superficial (al igual que la propagación, @Alizera)
Bogdan D

No puedes usar let in es5: ^) @Alireza
SensationSama

41

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. Puede decodificar tantas veces como desee para hacer tantas copias como necesite.

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

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

¿Por qué las funciones no pertenecen a JSON? Los he visto transferidos como JSON más de una vez ...
the_drow

55
Las funciones no son 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

1
@mark: { 'foo': function() { return 1; } }es un objeto construido literalmente.
abarnert

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 las asignaciones y todo tipo de cosas "no serializables".
vemv

36

Simplemente puede usar una propiedad de propagación para copiar un objeto sin referencias. Pero tenga cuidado (vea los comentarios), la 'copia' está justo 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'

Clon 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. Use una función de utilidad. Por ejemplo Ramda:

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


1
Esto no funciona ... funcionaría probablemente cuando x sea una matriz, por ejemplo x = ['ab', 'cd', ...]
Kamil Kiełczewski

3
Esto funciona, pero tenga en cuenta que esta es una copia SHALLOW, por lo tanto, cualquier referencia profunda a otros objetos sigue siendo una referencia.
Bugs Bunny

Un clon parcial también puede suceder de esta manera:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Cristian Traìna

26

Para aquellos que usan AngularJS, también hay 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 ...


2
Esta es una copia profunda para su información.
zamnuts

23

Aquí hay una función que puede usar.

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

10
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 del constructor Date inicializa la nueva Fecha con la fecha / hora actual. Ese valor no es enumerable y no será copiado por el ciclo for / in.
A. Levy

No es perfecto, pero agradable 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

Upvoted para llamar correctamente al constructor usando new. La respuesta aceptada no.
GetFree

funciona en nodo todo lo demás! todavía quedan enlaces de referencia
user956584

El pensamiento recursivo es genial. Pero si el valor es array, ¿funcionará?
Q10Viking

23

La respuesta de R. Levy está casi completa, aquí está mi pequeña contribución: hay una manera de manejar 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 exhaustivo estudio 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;
}

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


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

23

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

44
Esto está cerca, pero no funciona para ningún objeto. Intenta clonar un objeto Date con esto. No todas las propiedades son enumerables, por lo que no se mostrarán todas en el ciclo for / in.
A. Levy

Agregar al prototipo de objeto como este rompió jQuery para mí. Incluso cuando cambié el nombre a clone2.
iPadDeveloper2011

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

1
@Calvin: esto debe crearse como una propiedad no enumerable, de lo contrario, aparecerá 'clon' en los bucles 'for'.
mikemaccana

2
¿Por qué no es var copiedObj = Object.create(obj);una buena manera también?
Dan P.

20

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/


12
No clona profundamente el objeto.
Agosto

Esa es una tarea, no una copia. clone.Title = "solo un clon" significa que obj.Title = "solo un clon".
HoldOffHunger

@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";)
colapsar el

@collapsar: Eso es precisamente lo que verifiqué, luego console.log (persona) será "Whazzup", no "Thor Odinson". Ver el comentario de agosto.
HoldOffHunger

1
@HoldOffHunger No ocurre en Chrome 60.0.3112.113 ni en Edge 14.14393; El comentario de agosto no se aplica, ya que los valores de los tipos primitivos de objpropiedades de las propiedades están clonados. Los valores de propiedad que son Objetos en sí mismos no serán clonados.
collapsar

19

En ECMAScript 2018

let objClone = { ...obj };

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


1
¡Gracias por la pista 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

Esto es ES2016, no 2018, y esta respuesta se dio dos años antes .
Dan Dascalescu

¿y si quiero una copia de la propiedad anidada también
Sunil Garg

1
@SunilGarg Para copiar propiedades anidadas también puede usar const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

17

Usando Lodash:

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

55
Dios mío, sería una locura reinventar la clonación. Esta es la única respuesta sensata.
Dan Ross el

55
Prefiero _.cloneDeep(x)ya que esencialmente es lo mismo que arriba, pero se lee mejor.
garbanzio


14

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

1
+1 Object.create(...)parece definitivamente el camino a seguir.
René Nyffenegger

Respuesta perfecta. Tal vez podría agregar una explicación para Object.hasOwnProperty? De esa forma, la gente sabe cómo evitar buscar el enlace del prototipo.
froginvasion

Funciona bien, pero ¿en qué navegadores funciona el polyfill?
Ian Lunn

11
Esto está creando obj2 con un obj1 como prototipo. Solo funciona porque está sombreando al textmiembro en obj2. No está haciendo una copia, solo difiere de la cadena del prototipo cuando no se encuentra un miembro en obj2.
Nick Desaulniers

2
Esto NO lo crea "sin referencias", solo mueve la referencia al prototipo. Sigue siendo una referencia. Si una propiedad cambia en el original, también lo hará la propiedad prototipo en el "clon". No es un clon en absoluto.
Jimbo Jonny

13

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, lo que significa hacer una nueva copia de cada valor en cada objeto anidado recursivamente, requiere una de las soluciones más pesadas anteriores.

JavaScript sigue evolucionando.


2
no funciona cuando tienes funciones definidas en objetos
Petr Marek

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 iterable
Oleh

@Oleh, así que use `{... obj} en lugar de [... obj];`
manikant gautam
@manikantgautam Estaba usando Object.assign () antes, pero ahora la sintaxis de propagación de objetos es compatible con Chrome, Firefox (aún no en Edge y Safari). Su propuesta ECMAScript ... pero Babel lo admite hasta donde puedo ver, por lo que probablemente sea seguro de usar.
Oleh
12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

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

flori
fuente
¿Cómo es esto diferente de let cloned = Object.assign({}, obj)?
ceztko
10

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 los métodos vivos en el objeto clonado.

Por lo tanto, creo que una solución simple será primero serializar y deserializar y luego asignarle también funciones de copia.

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.

ConductedClever
fuente
Aunque si se me permite importar lodash, prefiero usar lodash cloneDeep.
ConductedClever
2
Estoy usando JSON.parse (JSON.stringify (fuente)). Siempre trabajando.
Misha
2
@Misha, de esta manera extrañarás las funciones. El término 'obras' tiene muchos significados.
ConductedClever
Y tenga en cuenta que, como 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
9

Para una copia profunda y un clon, JSON.stringify 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}}
Nishant Dwivedi
fuente
bastante inteligente ... alguna desventaja de este enfoque?
Aleks
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 clonado tendrá estas las propiedades apuntan a uno y el mismo clon del objeto referenciado. Esto también resuelve el caso de dependencias cíclicas que, si no se controla, conduce 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));
Radu Simionescu
fuente
1
A partir de septiembre de 2016, esta es la única solución correcta a la pregunta.
DomQ
6

Solo quería agregar a todas las Object.createsoluciones en esta publicación, que esto no funciona de la manera deseada con nodejs.

En Firefox el resultado de

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

es

{test:"test"}.

En nodejs es

{}
heinob
fuente
Esto es herencia prototípica, no clonación.
d13
1
@ d13 mientras su argumento es válido, tenga en cuenta que no hay una forma estandarizada en JavaScript para clonar un objeto. Esta es una herencia prototípica, pero se puede utilizar como clones si comprende los conceptos.
froginvasion
@froginvasion. El único problema con el uso de Object.create es que los objetos y matrices anidados son solo referencias de puntero a los objetos y matrices anidados del prototipo. jsbin.com/EKivInO/2/edit?js,console . Técnicamente, un objeto "clonado" debe tener sus propias propiedades únicas que no sean referencias compartidas a propiedades en otros objetos.
d13
@ d13 bien, ahora veo tu punto. Pero lo que quise decir es que demasiadas personas están alienadas con el concepto de herencia prototípica, y para mí no entiendo cómo funciona. Si no me equivoco, su ejemplo se puede solucionar simplemente llamando Object.hasOwnPropertypara verificar si posee la matriz o no. Sí, esto agrega complejidad adicional para lidiar con la herencia prototípica.
froginvasion
6
function clone(src, deep) {

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

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

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

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

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

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

    }

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

    return ret;
};
usuario1547016
fuente
2
if(!src && typeof src != "object"){. Creo que debería ser ||no &&.
MikeM
6

Como mindeavor declaró que el objeto que se va a clonar es un objeto 'construido literalmente', una solución podría ser simplemente generar el objeto varias veces en lugar de clonar una instancia del objeto:

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

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

He escrito mi propia implementación. No estoy seguro si cuenta como una mejor solución:

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

La siguiente es la implementación:

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

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

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

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

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

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

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

    return result;
}
yazjisuhail
fuente
no funciona para mi objeto, aunque mi caso es un poco complejo.
Sajuuk