Clonación Estructurada
El estándar HTML incluye un algoritmo de clonación / serializació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 es compatible con Fechas, RegExps, Mapas, Conjuntos, Blobs, Listas de archivos, ImageDatas, Matrices dispersas, Matrices escritas, y probablemente más en el futuro . También conserva referencias dentro de los datos clonados, lo que le permite soportar estructuras cíclicas y recursivas que causarían errores para JSON.
Soporte en Node.js: Experimental 🙂
El v8
módulo en Node.js actualmente (a partir del Nodo 11) expone la API de serialización estructurada directamente , pero esta funcionalidad todavía está marcada como "experimental" y está sujeta a cambios o eliminación en futuras versiones. Si está utilizando 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 esto, las implementaciones estructuradas de clones de los navegadores solo se exponen indirectamente.
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 un MessageChannels . El otro puerto emitirá un message
evento con un clon estructurado del adjunto .data
. 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 alternativas sincrónicas: ¡horrible! 🤢
No hay buenas opciones para crear clones estructurados sincrónicamente. Aquí hay un par de trucos poco prácticos en su lugar.
history.pushState()
y history.replaceState()
ambos crean un clon estructurado de su primer argumento y le asignan ese valor 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. Se 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 Notification
constructor 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();