Pregunta ¿Cómo puedo clonar correctamente un objeto de JavaScript?


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 incorporados de JavaScript 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 puedo clonar correctamente un objeto de JavaScript?


2395


origen


Respuestas:


Respuesta actualizada

Solo usa Object.assign () como se sugiere aquí

Pero tenga en cuenta que esto hace una copia superficial solamente. Los objetos anidados se siguen copiando como referencias.


Respuesta desactualizada

Hacer esto para cualquier objeto en JavaScript no será simple ni directo. Se encontrará 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 clone método para Object.prototype, como muestran algunas respuestas, tendrá que omitir explícitamente ese atributo. ¿Pero qué pasa si hay otros métodos adicionales agregados a Object.prototype, u otros prototipos intermedios, que usted no conoce? En ese caso, copiará atributos que no debería, por lo que debe detectar los atributos imprevistos y no locales con el hasOwnProperty 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, prototype es una propiedad oculta de una función. Además, el prototipo de un objeto se referencia con el atributo __proto__, que también está oculto y no será copiado por un bucle for / in que itere sobre los atributos del objeto fuente. creo __proto__ puede ser específico para el intérprete de JavaScript de Firefox y puede ser algo diferente en otros navegadores, pero se da cuenta de la situación. No todo es enumerable. Puede copiar un atributo oculto si conoce su nombre, pero no conozco ninguna forma de descubrirlo automáticamente.

Sin embargo, otro inconveniente 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 fuente es Object, simplemente creando un nuevo objeto general con {} 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ó al usar hasOwnProperty filtro, o que estaban en el prototipo, pero no eran enumerables en primer lugar. Una solución podría ser llamar al objeto fuente constructor propiedad para obtener el objeto de copia inicial y luego copiar sobre los atributos, pero aún así no obtendrás atributos no enumerables. Por ejemplo, un Date objeto 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 d1 será 5 segundos detrás de la de d2. Una forma de hacer uno Date lo mismo que otro es llamando al setTime método, pero eso es específico de la Date clase. No creo que haya una solución general a prueba de balas para este problema, ¡aunque me alegraría estar equivocado!

Cuando tuve que implementar una copia profunda general, terminé comprometiéndome asumiendo que solo necesitaría copiar un simple Object, Array, Date, String, Number, o Boolean. Los últimos 3 tipos son inmutables, por lo que podría realizar una copia superficial y no preocuparse de que cambie. Supuse además que cualquier elemento contenido en Object o Array tambié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 de JavaScript, pero puede ser suficiente para muchos propósitos, siempre y cuando no suponga que funcionará para cualquier cosa que le arroje.


1308



Con jQuery, puedes copia superficial con ampliar:

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

los cambios posteriores al objeto copiado no afectarán al objeto original, y viceversa.

O para hacer una copia profunda:

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

712



Si no usa funciones dentro de su objeto, un trazador de líneas muy simple puede ser el siguiente:

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

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

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


687



En ECMAScript 6 hay Object.assign método, que copia los valores de todas las propiedades 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.


508



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 sola 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á, haciendo 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 no hay bucle, no hay recursividad. Sin embargo, los navegadores más antiguos necesitarán un relleno policristalino.


113



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

Un Object.assign mé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 usa para copiar los valores de todas las propiedades enumerables de uno o más objetos de origen a un objeto de destino.

Lee mas...

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

105



Por MDN:

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

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


103



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

y = _.clone(x);

o puedes extenderlo como

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

70