javascript - unir - js clone variable




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

Nota: Esta es una respuesta a otra respuesta, no una respuesta adecuada a esta pregunta. Si desea una clonación rápida de objetos, siga los consejos de Corban en su respuesta a esta pregunta.

Quiero señalar que el método .clone() en jQuery solo clona elementos DOM. Para clonar objetos JavaScript, deberías hacer:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Se puede encontrar más información en la documentación de jQuery .

También quiero señalar que la copia profunda es en realidad mucho más inteligente que lo que se muestra arriba: puede evitar muchas trampas (por ejemplo, tratar de extender en profundidad un elemento DOM). Se usa con frecuencia en jQuery core y en complementos para un gran efecto.

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

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

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


Clonación Estructurada

HTML5 define un algoritmo de clonación interno "estructurado" que puede crear clones profundos de objetos. Todavía está limitado a ciertos tipos incorporados, pero además de los pocos tipos compatibles con JSON, también admite fechas, RegExps, mapas, conjuntos, blobs, listas de archivos, ImageDatas, arrays dispersos, arrays tipados y probablemente más en el futuro. . También conserva las referencias dentro de los datos clonados, lo que le permite soportar estructuras cíclicas y recursivas que podrían causar errores para JSON.

Soporte directo en los navegadores: ¿Próximamente? 🙂

Actualmente, los navegadores no proporcionan una interfaz directa para el algoritmo de clonación estructurada, pero se está discutiendo activamente una función global structuredClone() en whatwg / html # 793 en GitHub, ¡ y puede que esté disponible próximamente! Tal como se propone actualmente, su uso para la mayoría de los propósitos será tan simple como:

const clone = structuredClone(original);

Hasta que se envíe, las implementaciones de clones estructurados de los navegadores solo se exponen de manera indirecta.

Solución asíncrona: utilizable. 😕

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

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Ejemplo de uso:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Soluciones sincrónicas: horrible! 🤢

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

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

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

Ejemplo de uso:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

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

main();

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

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

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

Ejemplo de uso:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

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

main();


AngularJS

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

var newObject = angular.copy(oldObject);

Aquí hay una versión de la respuesta de ConroyP anterior que funciona incluso si el constructor ha requerido parámetros:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Esta función también está disponible en mi biblioteca simpleoo .

Editar:

Aquí hay una versión más robusta (gracias a Justin McCandless, ahora también admite referencias cíclicas):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

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

En mis pruebas anteriores, donde la velocidad era una de las principales preocupaciones que encontré

JSON.parse(JSON.stringify(obj))

para ser la forma más rápida de clonar en profundidad un objeto (supera a jQuery.extend con la jQuery.extend profunda establecida en un 10-20%).

jQuery.extend es bastante rápido cuando la marca de profundidad se establece en falso (clon superficial). Es una buena opción, ya que incluye un poco de lógica adicional para la validación de tipos y no copia propiedades no definidas, etc., pero esto también lo desacelerará un poco.

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

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

Los motores de traza de JavaScript apestan al optimizar para for..in bucles y verificar que hasOwnProperty también lo hará más lento. Clon manual cuando la velocidad es una necesidad absoluta.

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

Tenga cuidado al usar el JSON.parse(JSON.stringify(obj)) en los objetos Date - JSON.stringify(new Date()) devuelve una representación de cadena de la fecha en formato ISO, que JSON.parse() no vuelve a convertir a un objeto Date . Ver esta respuesta para más detalles .

Además, tenga en cuenta que, al menos en Chrome 65, la clonación nativa no es el camino a seguir. De acuerdo con este JSPerf , realizar una clonación nativa al crear una nueva función es casi 800x más lento que usar JSON.stringify, que es increíblemente rápido en todos los ámbitos.


Esto es lo que estoy usando:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

Sé que este es un post antiguo, pero pensé que esto podría ser de alguna ayuda para la siguiente persona que tropieza.

Mientras no asigne un objeto a nada, no mantiene ninguna referencia en la memoria. Entonces, para hacer un objeto que quieras compartir entre otros objetos, tendrás que crear una fábrica como esta:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

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

var newObject = _.clone(oldObject);

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

var newObject = JSON.parse(JSON.stringify(oldObject));

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

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

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

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

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2) ES5 +, utilizando JSON.parse y JSON.stringify.

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

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs & Loadash:

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

Espero que esta ayuda ...


Copia profunda por desempeño: clasificada de mejor a peor

  • Reasignación "=" (matrices de cadenas, matrices de números, solo)
  • Rebanada (matrices de cadenas, matrices de números - solamente)
  • Concatenación (matrices de cadenas, matrices de números - solamente)
  • Función personalizada: for-loop o copia recursiva.
  • $. extensión de jQuery
  • JSON.parse (arrays de cadenas, arrays de números, arrays de objetos - solo)
  • _Clone de Underscore.js (matrices de cadenas, matrices de números - solo)
  • Lo-Dash's _.cloneDeep

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

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

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

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

Y .slice () tiene mejor rendimiento que .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

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

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

Escriba una función personalizada (tiene un rendimiento más rápido que $ .extend () o JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Utilice funciones de utilidad de terceros:

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

Donde jQuery $ .extend tiene un mejor rendimiento:


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

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

var newObject = object(oldObject);

Es escueto, funciona como se espera y no necesita una biblioteca.

EDITAR:

Este es un polyfill para Object.create, por lo que también puede usar esto.

var newObject = Object.create(oldObject);

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

Por ejemplo si oldObject.a = 5;

newObject.a; // is 5

pero:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

Aquí hay un método clone () completo que puede clonar cualquier objeto JavaScript. Maneja casi todos los casos:

function clone(src, deep) {

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

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

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

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

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

    // Function
    if (toString.call(src) == "[object Function]") {

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

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

Copia superficial de una sola línea ( ECMAScript 5ª edición ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Y una copia superficial de una sola línea ( ECMAScript 6ª edición , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

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

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

Lodash tiene un buen lodash.com/docs#cloneDeep :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

Para las personas que desean usar la JSON.parse(JSON.stringify(obj))versión, pero sin perder los objetos de fecha, puede usar el segundo argumento del parsemétodo para convertir las cadenas de nuevo a fecha:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}

Parece que no hay un operador de clonación profunda ideal para los objetos de tipo matriz. Como se ilustra en el siguiente código, el clonador jQuery de John Resig convierte los arreglos con propiedades no numéricas en objetos que no son arreglos, y el clonador JSON de RegDwight elimina las propiedades no numéricas. Las siguientes pruebas ilustran estos puntos en múltiples navegadores:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }




clone