¿Cuál es la forma más eficaz de realizar una clonación profunda de un objeto en JavaScript?

Resuelto jschrab asked hace 15 años • 67 respuestas

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

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

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

jschrab avatar Sep 23 '08 23:09 jschrab
Aceptado

Clonación profunda nativa

Ahora existe un estándar JS llamado "clonación estructurada" , que funciona experimentalmente en el Nodo 11 y posteriores, llegará a los navegadores y tiene polyfills para los sistemas existentes .

structuredClone(value)

Si es necesario, cargue primero el polyfill:

import structuredClone from '@ungap/structured-clone';

Consulte esta respuesta para obtener más detalles.

Respuestas anteriores

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

Si no utiliza Dates, funciones undefined, InfinityRegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays u otros tipos complejos dentro de su objeto, una línea muy simple para clonar profundamente 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()
Expandir fragmento

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 reinventes la rueda : si ya estás usando una biblioteca, verifica 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()sólo 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.
Dan Dascalescu avatar Sep 23 '2008 18:09 Dan Dascalescu

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 profundamente un objeto (es más lento que jQuery.extend con deepel indicador establecido en verdadero en un 10-20%).

jQuery.extend es bastante rápido cuando el deepindicador está configurado 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á intentando 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 incorporando el procedimiento de clonación y construyendo manualmente el objeto.

Los motores de seguimiento de JavaScript son malos a la hora de optimizar for..inbucles y comprobar hasOwnProperty también lo ralentizará. Clonación manual cuando la velocidad es imprescindible.

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

Tenga cuidado al usar el 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 nuevamente 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. Según JSPerf, realizar una clonación nativa mediante la creación de una nueva función es casi 800 veces más lento que usar JSON.stringify, que es increíblemente rápido en todos los ámbitos.

Actualización para ES6

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

Object.assign({}, obj);
Corban Brook avatar Mar 17 '2011 19:03 Corban Brook

Clonación estructurada

Actualización 2022: la structuredClonefunción global ya está disponible en Firefox 94, Node 17 y Deno 1.14

El estándar HTML incluye un algoritmo interno estructurado de clonación/serialización 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 tipificadas y probablemente más en el futuro. . También conserva referencias dentro de los datos clonados, lo que le permite admitir estructuras cíclicas y recursivas que causarían errores en JSON.

Soporte en Node.js:

La structuredClonefunción global la proporciona el Nodo 17.0:

const clone = structuredClone(original);

Versiones anteriores: el v8módulo en Node.js (a partir del Nodo 11) expone la API de serialización estructurada directamente , pero esta funcionalidad aún está marcada como "experimental" y sujeta a cambios o eliminación en versiones futuras. Si estás 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: Disponible en Firefox 94

La structuredClonefunción global pronto será proporcionada por todos los principales navegadores (ya que se analizó anteriormente en whatwg/html#793 en GitHub ). Se ve/se verá así:

const clone = structuredClone(original);

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

Solución asincrónica: utilizable. 😕

La forma más sencilla de crear un clon estructurado con 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 archivo 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);

Uso de ejemplo:

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 alternativas sincrónicas: ¡horrible! 🤢

No existen buenas opciones para crear clones estructurados de forma sincrónica. 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. 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;
};

Uso de ejemplo:

Mostrar fragmento de código

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

Uso de ejemplo:

Mostrar fragmento de código

Jeremy avatar Jun 06 '2012 14:06 Jeremy

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

var newObject = JSON.parse(JSON.stringify(oldObject));
Sultan Shakir avatar Jan 04 '2011 08:01 Sultan Shakir