Pregunta

tengo un objeto, x.Me gustaría copiarlo como objeto. y, tal que cambia a y no modificar x.Me di cuenta de que copiar objetos derivados de objetos JavaScript integrados generará 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?

¿Fue útil?

Solución

Para hacer esto para cualquier objeto en JavaScript no será simple o sencillo. Que se ejecutará en el problema de elegir erróneamente a los atributos de prototipo del objeto que se debe dejar en el prototipo y no se copian a la nueva instancia. Si, por ejemplo, va a añadir un método clone a Object.prototype, ya que algunas respuestas representan, tendrá que saltar de forma explícita ese atributo. Pero lo que si no se añaden otros métodos adicionales para Object.prototype, u otros prototipos intermedios, que no sabe acerca de? En ese caso, se le copiar atributos que no debería, por lo que necesita para detectar atributos imprevistas, no locales con el método hasOwnProperty

Además de los atributos no enumerables, se encontrará con un problema más difícil cuando se intenta copiar objetos que han ocultado propiedades. Por ejemplo, prototype es una propiedad oculta de una función. Además, el prototipo de un objeto es referenciado con el atributo __proto__, que también se oculta, y no será copiado por una de / en la iteración del bucle sobre los atributos del objeto de origen. Creo __proto__ podría ser específico para intérprete de JavaScript de Firefox y que puede ser algo diferente en otros navegadores, pero se obtiene la imagen. No todo es enumerable. Se puede copiar un atributo oculto si conoce su nombre, pero no sé de ninguna manera de descubrir automáticamente.

Sin embargo, otro obstáculo en la búsqueda de una solución elegante es el problema de la creación de la herencia de prototipo correctamente. Si se Object prototipo de su objeto de origen, entonces la simple creación de un nuevo objeto general con {} va a funcionar, pero si el prototipo de la fuente es algún descendiente de Object, entonces van a faltar los miembros adicionales de ese prototipo que se ha saltado el uso de la hasOwnProperty filtro, o los que estaban en el prototipo, pero no eran numerables en el primer lugar. Una solución podría ser la de llamar a la propiedad constructor del objeto de origen para obtener el objeto inicial de copias y luego copiar los atributos, pero entonces todavía no conseguirá atributos no enumerables. Por ejemplo, objeto un Date 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 fecha para d1 será de 5 segundos por detrás de la de d2. Una manera de hacer una Date el mismo que el otro es llamando al método setTime, pero que es específico de la clase Date. Creo que no hay una solución general a prueba de balas a este problema, aunque yo estaría feliz de estar equivocado!

Cuando tenía que poner en práctica el copiado general profunda terminé comprometer asumiendo que sólo tendría que copiar una llanura Object, Array, Date, String, Number o Boolean. Los 3 últimos tipos son inmutables, por lo que podían realizar una copia superficial y no preocuparse de lo que cambia. I supone además que los elementos contenidos en Object o Array también serían uno de los 6 tipos simples en esa lista. Esto se puede lograr con 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 he mencionado, siempre que los datos de los objetos y las matrices forman 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 va a ser capaz de manejar cualquier objeto de JavaScript, pero puede ser suficiente para muchos purposes, siempre y cuando no suponga que se acaba de trabajar para cualquier cosa que lanzar en él.

Otros consejos

Si usted no utiliza Dates, funciones, indefinido o infinito dentro de su objeto, una muy simple revestimiento 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.

este artículo sobre el algoritmo clon estructurado de navegadores que se utiliza cuando la publicación de mensajes hacia y desde un trabajador. También contiene una función para la clonación de profundidad.

Con jQuery, puede copia superficial extender :

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

cambios posteriores en la copiedObject no afectarán a la originalObject, y viceversa.

O para hacer una copia profunda

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

En ECMAScript 6 hay un 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.

MDN :

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

No hay necesidad de bibliotecas externas, pero es necesario comprobar compatibilidad del navegador primera .

Hay muchas respuestas, pero ninguno de ellos menciona Object.create partir de ECMAScript 5, que ciertamente no le da una copia exacta, pero establece el origen como el prototipo del nuevo objeto.

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

  1. Cuando esa herencia es útil (la!)
  2. Cuando no se modificará el objeto de origen, con lo que la relación entre los 2 objetos un no tema.

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 sea superior? Es nativa, por lo que no loop, sin recursividad. Sin embargo, los navegadores antiguos necesitarán un polyfill.

Una forma elegante de clonar un objeto Javascript en una línea de código

Un método Object.assign es parte de la especificación de ECMAScript 2015 (ES6) estándar 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.

Leer más ...

La polyfill para soportar los 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;
    }
  });
}

Hay varios problemas con la mayoría de soluciones en Internet. Así que decidí hacer un seguimiento, que incluye, por eso la respuesta aceptada no debe ser aceptada.

situación de partida

Quiero profunda copia a Object Javascript con todos sus hijos y sus hijos y así sucesivamente. Pero como yo no soy una especie de desarrollador normal, mi Object tiene Normal properties, circular structures e incluso nested objects.

Así que vamos a crear un circular structure y una nested object primero.

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

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

Vamos a traer todo junto en un Object llamado a.

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

A continuación, queremos copiar a en una variable llamada b y mutar a él.

var b = a;

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

Usted sabe lo que pasó aquí porque si no que ni siquiera la tierra en esta gran cuestión.

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 vamos a encontrar una solución.

JSON

El primer intento lo que probamos estaba usando JSON.

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

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

No pierda demasiado tiempo en ella, obtendrá TypeError: Converting circular structure to JSON.

copia recursiva (la aceptada "respuesta")

Vamos a echar 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 se ocupa de otros tipos, así como Date, pero eso no era un requisito.

var b = cloneSO(a);

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

La recursividad y circular structures no trabajan 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é había pasado, y se encontró un simple Solución: después de algunas google. Se llama Object.create.

var b = Object.create(a);

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

Esta solución se añadió 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, que no funcionó con la estructura anidada dentro.

polyfill para la solución nativa

Hay una polyfill para Object.create en el navegador más antiguo al igual que el IE 8. Es algo así como recomendado por Mozilla, y por supuesto, no es perfecto y los resultados en el mismo problema que el 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';

He puesto F fuera del alcance para que podamos echar un vistazo a lo que nos dice instanceof.

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 como el solución nativa , pero una salida poco peor.

la solución mejor (pero no perfecta)

Al excavar alrededor, me encontré con una pregunta similar ( En Javascript, al realizar una copia profunda, ¿cómo puedo evitar un ciclo, debido a una propiedad de ser 'esto'? ) a éste, pero con una solución mejor manera.

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 vamos a echar 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
se hacen coincidir

Los requisitos, pero todavía hay algunos problemas menores, incluyendo el cambio del instance de nested y circ a 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 utilizando la recursividad y una memoria caché, puede no ser el mejor, pero es un real profunda copia del objeto. Maneja properties sencilla, circular structures y nested object, pero se hace un lío el caso de ellos, mientras que la clonación.

jsFiddle

Si estás bien con una copia superficial, la biblioteca tiene un underscore.js clonar método.

y = _.clone(x);

o puede extenderlo como

copiedObject = _.extend({},originalObject);

Aceptar imagine que tiene este objeto a continuación y que desea clonarlo:

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

o

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

la respuesta es depeneds principalmente en que ECMAscript se utiliza, en ES6+, sólo tiene que utilizar Object.assign hacer el clon:

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

o usar el operador propagación de esta manera:

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

Pero si el uso de ES5, puede utilizar algunos métodos, pero el JSON.stringify, sólo asegúrese de que no se utiliza para una gran parte de los datos a copiar, pero podría ser una línea de forma práctica 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

Una solución especialmente poco elegante es utilizar JSON codificación para hacer copias en profundidad de los objetos que no tienen métodos miembros. La metodología consiste en JSON codificar el objeto de destino, a continuación, mediante la decodificación, se obtiene la copia que busca. Puede decodificar tantas veces como desee para hacer tantas copias como sea necesario.

Por supuesto, las funciones no tienen cabida en JSON, por lo que esto sólo funciona para objetos sin métodos miembros.

Esta metodología fue perfecto para mi caso de uso, ya que estoy almacenando manchas JSON en un almacén de claves-valor, y cuando son expuestos como objetos en una API de JavaScript, cada objeto en realidad contiene una copia del estado original de la objeto, de manera que podemos calcular el delta después de 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

Usted puede simplemente utilizar un difundir la propiedad a copiar un objeto sin referencias. Pero cuidado (ver comentarios), la 'copia' es sólo en el nivel de objeto / gama más baja. 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'

Clone con referencias en el 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 realidad no soporta de forma nativa clones profundas. Utilizar una función de utilidad. Por ejemplo Ramda:

  

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

Para aquellos que utilizan AngularJS, también hay método directo para la clonación o la ampliación de los objetos en esta biblioteca.

var destination = angular.copy(source);

o

angular.copy(source, destination);

Más en la documentación href="https://docs.angularjs.org/api/ng/function/angular.copy"> ...

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

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

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

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

Inspirado por estudio exhaustivo de A.Levy y el enfoque de prototipos de Calvino, 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;
}

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

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

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

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

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

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

En ECMAScript 2018

let objClone = { ...obj };

Tenga en cuenta que objetos anidados se fija copiada como referencia.

Puede clonar un objeto y eliminar cualquier referencia de la anterior utilizando una sola línea de código. Basta con hacer:

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

¡Nueva respuesta a una vieja pregunta!Si tiene el placer de utilizar ECMAScript 2016 (ES6) con Sintaxis extendida, 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 recursivamente, requiere una de las soluciones más pesadas anteriores.

JavaScript sigue evolucionando.

let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

solución ES6 si quieres (superficial) clonar un instancia de clase y no sólo un objeto de propiedad.

Interesado en objetos sencillos de clonación:

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

Fuente: Cómo copiar objetos JavaScript para nueva variable no por referencia?

El uso de Lodash:

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

Creo que hay una forma sencilla y trabajar respuesta. En la copia profunda hay dos preocupaciones:

  1. Mantenga propiedades independientes entre sí.
  2. Y mantener los métodos viva en el objeto clonado.

Así que creo que una solución sencilla será la primera serializar y deserializar y luego hacer un Asignar en él para las funciones de copia también.

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

A pesar de que esta pregunta tiene muchas respuestas, espero que éste también ayuda.

Para obtener una copia profunda y clon, JSON.stringify entonces 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}}

Sólo quería añadir a todas las soluciones Object.create en este post, 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

{}

Esta es una adaptación del código de A. Levy para manejar también la clonación de funciones y múltiples referencias / cíclicos - 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 propiedades apuntan a uno y el mismo clon del objeto referenciado. Esto también resuelve el caso de dependencias cíclicas que, si se deja no controlada, 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));
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;
};

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

/*
    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)    /
*/

A continuación se presenta la aplicació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;
}

Respuesta de Jan Turoň anterior está muy cerca, y puede ser la mejor manera de utilizar en un navegador, debido a problemas de compatibilidad, pero potencialmente causará algunos problemas de enumeración extraños. Por ejemplo, la ejecución de:

for ( var i in someArray ) { ... }

asignará el método clone () para i después de la iteración a través de los elementos de la matriz. Aquí está una adaptación que evita la enumeración y trabaja con Node.js:

Object.defineProperty( Object.prototype, "clone", {
    value: 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;
    }
});

Object.defineProperty( Date.prototype, "clone", {
    value: function() {
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    }
});

Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

Esto evita haciendo que el método clone () enumerable porque defineProperty () por defecto enumerable a falso.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top