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

5174

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

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

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

7
  • 566
    Eval no es malvado. Usar eval mal es. Si tiene miedo de sus efectos secundarios, lo está usando mal. Los efectos secundarios que temes son las razones para usarlo. Por cierto, ¿alguien respondió realmente a tu pregunta? Heavy Gray 22/03/12 a las 14:08
  • 15
    La clonación de objetos es un asunto complicado, especialmente con objetos personalizados de colecciones arbitrarias. Probablemente por eso no existe una forma inmediata de hacerlo. b01 11/0313 a las 22:25
  • 12
    eval()es generalmente una mala idea porque muchos optimizadores del motor Javascript tienen que apagarse cuando se trata de variables que se establecen a través deeval . El solo hecho de tenerlo eval()en su código puede conducir a un peor rendimiento. user56reinstatemonica8 8 de septiembre de 2014 a las 13:37
  • 2
    Posible duplicado de la forma más elegante de clonar un objeto JavaScriptJohn Slegers 21/02/2016 a las 18:21
  • 12
    Tenga en cuenta que el JSONmétodo perderá cualquier tipo de Javascript que no tenga equivalente en JSON. Por ejemplo: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))generará{a: null, b: null, c: null, g: false}oriadam 24 de mayo de 2017 a las 13:06
5214

Clonación profunda nativa

Se llama "clonación estructurada", funciona de forma experimental en el Nodo 11 y posteriores, y es de esperar que llegue a los navegadores. Consulte esta respuesta para obtener más detalles.

Clonación rápida con pérdida de datos: JSON.parse / stringify

Si no se utiliza Dates, funciones undefined, Infinity, expresiones regulares, Mapas, Conjuntos, gotas, filelists, ImageDatas, matrices dispersas, Conjuntos escritos u otros tipos complejos dentro de su objeto, un revestimiento muy simple uno a lo profundo del clon de un objeto 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'
  re: /.*/,  // lost
}
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()

Consulte la respuesta de Corban para conocer los puntos de referencia.

Clonación confiable usando una biblioteca

Dado que la clonación de objetos no es trivial (tipos complejos, referencias circulares, funciones, etc.), la mayoría de las bibliotecas principales proporcionan funciones para clonar objetos. No reinvente la rueda : si ya está usando una biblioteca, verifique si tiene una función de clonación de objetos. Por ejemplo,

  • lodash — cloneDeep; se puede importar por separado a través del módulo lodash.clonedeep y probablemente sea su mejor opción si aún no está utilizando una biblioteca que proporcione una función de clonación profunda
  • AngularJS - angular.copy
  • jQuery - jQuery.extend(true, { }, oldObject); .clone()solo clona elementos DOM
  • solo biblioteca - just-clone; Parte de una biblioteca de módulos npm de dependencia cero que solo hacen una cosa. Utilidades libres de culpa para cada ocasión.

ES6 ( copia superficial )

Para completar, tenga en cuenta que ES6 ofrece dos mecanismos de copia superficial: Object.assign()y la sintaxis de propagación . que copia valores de todas las propiedades propias enumerables de un objeto a otro. Por ejemplo:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax
12
  • 79
    ¡Tener cuidado! var A = { b: [ { a: [ 1, 2, 3], b: [4, 5, 6], c: [7, 8, 9] } ] }; B = Object.assign( {}, A ); delete B.b[0].b;¡También modificará el objeto A! Gabriel Hautclocq 30 de septiembre de 2020 a las 12:54
  • 8
    @Gabriel Hautclocq esto se debe a que A.bo B.bambos se refieren al mismo objeto en la memoria. si Atuviera una propiedad con un valor que no sea de objeto (como números o cadenas), se copiará normalmente. Pero cuando se copia una propiedad que contiene un valor de objeto, se copia por referencia, no por valor. Además, tenga en cuenta que una matriz es un objeto en JS. prueba:typeof [] == 'object' && [] instanceof ArrayUnicornist 15/12/20 a las 13:21
  • 24
    @Unicornist Sí y es por eso que Object.assign no responde a la pregunta: "¿Cuál es la forma más eficiente de clonar en profundidad un objeto en JavaScript?". Por lo tanto, al menos NO debe presentarse como una solución ES6 para la clonación profunda. El título "ES6" es engañoso, al menos debería cambiarse para reflejar que este no es un método de clonación profunda. La palabra "superficial" es fácil de pasar por alto y muchas personas simplemente toman la solución más simple que encuentran en Stack Overflow sin leerlo todo. Es peligroso confiar en Object.assign para la clonación de objetos. De ahí mi observación. Gabriel Hautclocq 15/12/20 a las 13:58
  • 2
    Usé una biblioteca llamada clonación profunda realmente rápida: github.com/davidmarkclements/rfdc Funcionó muy bien para mí. bakkaa 14 de enero a las 16:02
  • 1
    Si experimenta problemas como en el primer comentario, github.com/davidmarkclements/rfdc también hará una "copia superficial", deberá habilitar la opción proto: true,Dmitry 8 de marzo a las 10:48
2372

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

En mis pruebas anteriores, donde la velocidad era una preocupación principal, encontré

JSON.parse(JSON.stringify(obj))

para ser la forma más lenta de clonar en profundidad un objeto (es más lento que jQuery.extend con el deepindicador establecido como verdadero en un 10-20%).

jQuery.extend es bastante rápido cuando la deepbandera está configurada en false(clon superficial). Es una buena opción, porque incluye algo de lógica adicional para la validación de tipos y no copia propiedades no definidas, etc., pero esto también lo ralentizará un poco.

Si conoce la estructura de los objetos que está tratando de clonar o puede evitar matrices anidadas profundas, puede escribir un for (var i in obj)bucle simple para clonar su objeto mientras verifica 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 rastreo de JavaScript apestan a la for..inhora de optimizar los bucles y comprobar hasOwnProperty también lo ralentizará. Clon manual cuando la velocidad es una necesidad absoluta.

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

Tenga cuidado con el uso del JSON.parse(JSON.stringify(obj))método en Dateobjetos: JSON.stringify(new Date())devuelve una representación de cadena de la fecha en formato ISO, que JSON.parse() no se convierte de nuevo en un Dateobjeto. Consulte esta respuesta para obtener 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 JSPerf, realizar la clonación nativa mediante la creación de una nueva función es casi 800x más lento que usar JSON.stringify que es increíblemente rápido todo el camino a través del tablero.

Actualización para ES6

Si está utilizando Javascript ES6, pruebe este método nativo para la clonación o copia superficial.

Object.assign({}, obj);
1
  • 3
    Tenga en cuenta que hay 2 errores en su banco: primero, compara una clonación superficial (lodash _.cloney Object.assign) con una clonación profunda ( JSON.parse(JSON.stringify())). En segundo lugar, dice "clon profundo" para lodash, pero en su lugar hace un clon superficial. papillon 15 feb a las 14:22
517

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

var newObject = JSON.parse(JSON.stringify(oldObject));
2
  • 12
    Los objetos tienen propiedades, no variables. ;-)RobG 1/10/20 a las 0:42
  • funciones y fechas tambiénvsync 24/10/20 a las 12:54
469
+50

Clonación estructurada

El estándar HTML incluye un algoritmo de serialización / clonación estructurado interno que puede crear clones profundos de objetos. Todavía está limitado a ciertos tipos integrados, pero además de los pocos tipos admitidos por JSON, también admite fechas, expresiones regulares, mapas, conjuntos, blobs, listas de archivos, datos de imágenes, matrices dispersas, matrices con tipo y probablemente más en el futuro. . También conserva las referencias dentro de los datos clonados, lo que le permite admitir estructuras cíclicas y recursivas que causarían errores para JSON.

Soporte en Node.js: Experimental 🙂

El v8módulo en Node.js actualmente (a partir del Nodo 11) expone la API de serialización estructurada directamente , pero esta funcionalidad aún está marcada como "experimental" y está sujeta a cambios o eliminación en versiones futuras. Si está usando una versión compatible, clonar un objeto es tan simple como:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Soporte directo en navegadores: ¿Quizás eventualmente? 😐

Actualmente, los navegadores no proporcionan una interfaz directa para el algoritmo de clonación estructurada, pero structuredClone()se ha discutido una función global en whatwg / html # 793 en GitHub . Como se propone actualmente, usarlo para la mayoría de los propósitos sería tan simple como:

const clone = structuredClone(original);

A menos que se envíe, las implementaciones de clones estructurados de los navegadores solo se exponen indirectamente.

Solución alternativa asincrónica: 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 messageevento con un clon estructurado del adjunto .data. Desafortunadamente, escuchar estos eventos es necesariamente asincrónico 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 de forma sincrónica. En su lugar, aquí hay un par de trucos poco prácticos.

history.pushState()y history.replaceState()ambos crean un clon estructurado de su primer argumento y asignan ese valor a history.state. Puede 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 Notificationconstructor 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 permiso de notificación. En caso de que tenga el 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();
4
  • 47
    ¡Esto está tan mal! Esa API no está diseñada para usarse de esta manera. Fardin K. 31/07/14 a las 23:34
  • 270
    Como el tipo que implementó pushState en Firefox, siento una extraña mezcla de orgullo y repulsión por este truco. Bien hecho muchachos. Justin L. 14 de agosto de 2014 a las 18:37
  • 1
    El truco pushState o Notification no funciona para algunos tipos de objetos como FunctionShishir Arora 3 de julio de 2019 a las 20:06
  • @ShishirArora Tienes razón, lo acabo de intentar, arroja una 'DOMException no detectada: el objeto no se pudo clonar'. Esto también es válido para el truco de notificaciones. ADJenks 10 jul.20 a las 21:39
352

Si no hubiera uno incorporado, podría intentar:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}
0
160

La forma eficiente de clonar (no clonar en profundidad) un objeto en una línea de código

Un Object.assignmétodo es parte del estándar ECMAScript 2015 (ES6) y hace exactamente lo que necesita.

var clone = Object.assign({}, obj);

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

Lee mas...

El polyfill para admitir navegadores más antiguos:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}
4
  • 93
    Esto no se copia de forma recursiva, por lo que realmente no ofrece una solución al problema de clonar un objeto. mwhite 8 de marzo de 2016 a las 19:56
  • 5
    Este método funcionó, aunque probé algunos y _.extend ({}, (obj)) fue POR LEJOS el más rápido: 20 veces más rápido que JSON.parse y 60% más rápido que Object.assign, por ejemplo. Copia todos los subobjetos bastante bien. Nico 9 de mayo de 2016 a las 19:57
  • 12
    @mwhite hay una diferencia entre clonar y clonar en profundidad. Esta respuesta de hecho se clona, ​​pero no se clona en profundidad. Meirion Hughes 8 de junio de 2016 a las 12:08
  • la pregunta era sobre copias recursivas. Object.assign, así como la asignación personalizada dada, no se copian de forma recursivajohannes_lalala 4 de marzo a las 1:52
109

Código:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

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

    return to;
}

Prueba:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);
1
  • No me gusta esto maneja estructuras circularesGershy 27 abr a las 17:42
109

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;
}
2
  • Intentando: var a = {b: 1, c: 3, d: {a: 10, g: 20, h: {today: new Date ()}}}; No funciona para mi. Pero lo Object.assign({}, a)hizo. iMartin 8 de abril a las 19:58
  • Peor aún, intentelet o = {}; o.o = o; cloneObject(o); ... Gershy 27 abr a las 17:42
91

Copiar objetos en profundidad en JavaScript (creo que el mejor y el más simple)

1. Usando JSON.parse (JSON.stringify (objeto));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.Usando el método creado

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

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Usando el enlace _.cloneDeep de Lo-Dash lodash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Usando el método Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

PERO INCORRECTO CUANDO

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Uso de Underscore.js _.clone link Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

PERO INCORRECTO CUANDO

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Rendimiento Copia profunda de objetos en JavaScript

3
  • 1
    Oye, tu último ejemplo está mal. En mi opinión, debe usar _clone y no _cloneDeep para el ejemplo incorrecto. kenanyildiz90 10/12/19 a las 7:20
  • Este método creado (2.) no funcionará para matrices, ¿verdad? Toivo Säwén 18 dic 2019 a las 14:06
  • El método n. ° 2 es vulnerable a la contaminación del prototipo, similar a lo que sucedió con los lodash defaultsDeep. No debería copiar si (i === '__proto__'), y no debería copiar si (i === 'constuctor' && typeof obj[i] === 'function'). Frank Fajardo 7 de marzo a las 8:46
84

Copia profunda por rendimiento: clasificado de mejor a peor

  • Reasignación "=" (matrices de cadenas, matrices de números, solo)
  • Slice (matrices de cadenas, matrices de números, solo)
  • Concatenación (matrices de cadenas, matrices de números, solo)
  • Función personalizada: copia recursiva o for-loop
  • $ .extend de jQuery
  • JSON.parse (matrices de cadenas, matrices de números, matrices de objetos, solo)
  • _.Clone de Underscore.js (matrices de cadenas, matrices de números, solo)
  • _.CloneDeep de Lo-Dash

Copia 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 profunda 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 un 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

Copia 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 $ .extend de jQuery tiene un mejor rendimiento:

2
  • 1
    Con el bucle for-in, debe utilizar hasOwnPropertypara excluir propiedades heredadas. Yo uso (posiblemente incluso más rápido) simple para bucle Object.keys. mikiqex 29 de junio a las 6:19
  • 1
    En una copia profunda, ¿no le gustaría copiar también las propiedades heredadas? Además, tenga en cuenta que al invocar el hasOwnPropertymétodo, se crea un impacto de rendimiento (presionando la llamada a la función dentro y fuera de la pila, y ejecutando el código del método) para cada tecla. tim-montague 30 de junio a las 17:47
69

Cloning un Objeto siempre fue una preocupación en JS, pero se trataba de antes 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 profunda 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 +, usando 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] = deepCopyObj(obj[i]);
            }
            return copy;
        }
        if (obj instanceof Object) {
            var copy = {};
            for (var attr in obj) {
                if (obj.hasOwnProperty(attr)) copy[attr] = deepCopyObj(obj[attr]);
            }
            return copy;
        }
        throw new Error("Unable to copy obj this object.");
    }
  1. ES5 +, usando JSON.parse y JSON.stringify.
    var  deepCopyObj = JSON.parse(JSON.stringify(obj));
  1. AngularJs:
    var  deepCopyObj = angular.copy(obj);
  1. jQuery:
    var deepCopyObj = jQuery.extend(true, {}, obj);
  1. UnderscoreJs y Loadash:
    var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Espero que estos ayuden ...

1
  • Gracias por el de jQuery, ¡genial! tim 2 de junio a las 10:11
67
var clone = function() {
    var newObj = (this instanceof Array) ? [] : {};
    for (var i in this) {
        if (this[i] && typeof this[i] == "object") {
            newObj[i] = this[i].clone();
        }
        else
        {
            newObj[i] = this[i];
        }
    }
    return newObj;
}; 

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
0
61

Hay una biblioteca (llamada "clon") que lo hace bastante bien. Proporciona la clonación / copia recursiva más completa de objetos arbitrarios que conozco. También admite referencias circulares, que aún no están cubiertas por las otras respuestas.

También puede encontrarlo en npm . Se puede utilizar tanto para el navegador como para Node.js.

Aquí hay un ejemplo de cómo usarlo:

Instalarlo con

npm install clone

o empaquételo con Ender .

ender build clone [...]

También puede descargar el código fuente manualmente.

Entonces puedes usarlo en tu código fuente.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Descargo de responsabilidad: soy el autor de la biblioteca).

0
54

Sé que esta es una publicación antigua, pero pensé que podría ser de alguna ayuda para la próxima persona que se tropiece.

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

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

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

var newObject = _.clone(oldObject);
0
45

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

//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://stackoverflow.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();
    };
}
31

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

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

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

var newObject = object(oldObject);

Es conciso, funciona como se esperaba y no necesita una biblioteca.


EDITAR:

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

var newObject = Object.create(oldObject);

NOTA: Si usa algo de esto, puede tener problemas con algunas iteraciones que usan 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
0
23

Lodash tiene un buen método _.cloneDeep (value) :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
0
22
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
0
22

Copia superficial de una sola línea ( ECMAScript 5ta 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 copia superficial de una sola línea ( ECMAScript 6ta 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
0
18

Parece que todavía no hay un operador de clonación profunda ideal para objetos tipo matriz. Como ilustra el código siguiente, el clonador jQuery de John Resig convierte matrices con propiedades no numéricas en objetos que no son matrices, y el clonador JSON de RegDwight elimina las propiedades no numéricas. Las siguientes pruebas ilustran estos puntos en varios 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)
0
18

Solo porque no vi a AngularJS mencionado y pensé que la gente podría querer saber ...

angular.copy también proporciona un método de copia profunda de objetos y matrices.

2
  • o podría usarse de la misma manera que jQiery extend:angular.extend({},obj);Galvani 21 de septiembre de 2016 a las 9:07
  • 2
    @Galvani: Cabe señalar que jQuery.extendy angular.extendambos son copias superficiales. angular.copyes una copia profunda. Dan Atkinson 15/10/2016 a las 18:41
17

Tengo dos buenas respuestas dependiendo de si su objetivo es clonar un "objeto JavaScript simple y antiguo" o no.

Supongamos también que su intención es crear un clon completo sin referencias de prototipo al objeto de origen. Si no está interesado en un clon completo, puede usar muchas de las rutinas Object.clone () proporcionadas en algunas de las otras respuestas (patrón de Crockford).

Para objetos de JavaScript antiguos y sencillos, una buena manera probada y verdadera de clonar un objeto en tiempos de ejecución modernos es bastante simple:

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

Tenga en cuenta que el objeto de origen debe ser un objeto JSON puro. Es decir, todas sus propiedades anidadas deben ser escalares (como booleano, cadena, matriz, objeto, etc.). Las funciones u objetos especiales como RegExp o Date no se clonarán.

¿Es eficiente? Diablos, sí. Hemos probado todo tipo de métodos de clonación y esto funciona mejor. Estoy seguro de que algún ninja podría inventar un método más rápido. Pero sospecho que estamos hablando de ganancias marginales.

Este enfoque es simple y fácil de implementar. Envuélvalo en una función de conveniencia y si realmente necesita exprimir algo de ganancia, hágalo más adelante.

Ahora bien, para los objetos JavaScript que no son simples, no hay una respuesta realmente simple. De hecho, no puede haberlo debido a la naturaleza dinámica de las funciones de JavaScript y el estado del objeto interno. La clonación profunda de una estructura JSON con funciones en su interior requiere que vuelva a crear esas funciones y su contexto interno. Y JavaScript simplemente no tiene una forma estandarizada de hacerlo.

La forma correcta de hacer esto, una vez más, es a través de un método de conveniencia que declare y reutilice dentro de su código. El método de conveniencia puede dotarse de cierta comprensión de sus propios objetos para que pueda asegurarse de recrear correctamente el gráfico dentro del nuevo objeto.

Estamos escritos por nosotros mismos, pero el mejor enfoque general que he visto está cubierto aquí:

http://davidwalsh.name/javascript-clone

Ésta es la idea correcta. El autor (David Walsh) ha comentado la clonación de funciones generalizadas. Esto es algo que puede optar por hacer, según su caso de uso.

La idea principal es que necesita un manejo especial de la instanciación de sus funciones (o clases prototípicas, por así decirlo) por tipo. Aquí, ha proporcionado algunos ejemplos para RegExp y Date.

Este código no solo es breve, sino que también es muy legible. Es bastante fácil de ampliar.

¿Es esto eficiente? Diablos, sí. Dado que el objetivo es producir un verdadero clon de copia profunda, entonces tendrá que recorrer los miembros del gráfico del objeto de origen. Con este enfoque, puede ajustar exactamente qué miembros secundarios tratar y cómo manejar manualmente los tipos personalizados.

Ahí vas. Dos enfoques. Ambos son eficientes en mi opinión.

16

Llego tarde para responder a esta pregunta, pero tengo otra forma de clonar el objeto:

function cloneObject(obj) {
    if (obj === null || typeof(obj) !== 'object')
        return obj;
    var temp = obj.constructor(); // changed
    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = cloneObject(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

var b = cloneObject({"a":1,"b":2});   // calling

que es mucho mejor y más rápido que:

var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));  

y

var a = {"a":1,"b":2};

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

He realizado una evaluación comparativa del código y puede probar los resultados aquí :

y compartir los resultados: ingrese la descripción de la imagen aquí Referencias: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

4
  • Es gracioso, pero cuando ejecuto tus pruebas, en realidad me indico que el método 1 es el más lentoAntoniossss 26 abr 2018 a las 8:30
  • ¡Igual que yo, el bloque 1 es el más bajo! SPG 5/12/18 a las 1:08
  • ¡La única solución que funcionó para mí! Tuve que clonar en profundidad un objeto que contenía otros objetos con propiedades de función. Perfecto. Phoenix 12 de marzo a las 17:22
  • ¿Por qué lo configura obj['isActiveClone'] = nully luego lo elimina? ¿Y por qué no llamas obj.hasOwnProperty(key)? Aykut Kllic 16 de abril a las 7:50
15

Por lo general, esta no es la solución más eficiente, pero hace lo que necesito. Casos de prueba simples a continuación ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Prueba de matriz cíclica ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Prueba de funcionamiento...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
14

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

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(obj), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v)
    return v;
  })
}

// usage:
var original = {
 a: [1, null, undefined, 0, {a:null}, new Date()],
 b: {
   c(){ return 0 }
 }
}

var cloned = clone(original)

console.log(cloned)
1
  • No es un clon del 100%vsync 24 oct.20 a las 13:07
13

Solo cuando pueda utilizar ECMAScript 6 o transpilers .

Características:

  • No activará getter / setter mientras copia.
  • Conserva getter / setter.
  • Conserva la información del prototipo.
  • Funciona con estilos de escritura OO literal de objeto y funcional .

Código:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}
13

No estoy de acuerdo con la respuesta con más votos aquí . Un clon profundo recursivo es mucho más rápido que el enfoque JSON.parse (JSON.stringify (obj)) mencionado.

Y aquí está la función para referencia rápida:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}
3
  • 2
    Me gustó este enfoque, pero no maneja las fechas correctamente; considere agregar algo como if(o instanceof Date) return new Date(o.valueOf());después de verificar nulo `Luis 21/08/2017 a las 22:53
  • Se bloquea en referencias circulares. Harry 18/03/18 a las 5:19
  • En el último Firefox estable, esto es mucho más largo que las otras estrategias en ese enlace Jsben.ch, en un orden de magnitud o más. Golpea a los demás en la dirección equivocada. WBT 14/01/19 a las 18:38
12

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;
};
2
  • Convierte primitivas en objetos envoltorios, lo que no es una buena solución en la mayoría de los casos. Danubian Sailor 1 de agosto de 2014 a las 9:58
  • @DanubianSailor: no creo que lo haga ... parece devolver primitivas desde el principio, y no parece estar haciendo nada que las convierta en objetos de envoltura a medida que se devuelven. Jimbo Jonny 2 feb 2016 a las 18:06