Clonazione strutturata
Lo standard HTML include un algoritmo di clonazione / serializzazione strutturato interno che può creare cloni profondi di oggetti. È ancora limitato a determinati tipi predefiniti, ma oltre ai pochi tipi supportati da JSON supporta anche Date, RegExps, Mappe, Set, BLOB, FileList, ImageDatas, Array sparsi, Array tipizzati e probabilmente altri in futuro . Conserva anche i riferimenti all'interno dei dati clonati, consentendogli di supportare strutture cicliche e ricorsive che potrebbero causare errori per JSON.
Supporto in Node.js: sperimentale 🙂
Il v8
modulo in Node.js attualmente (a partire dal Nodo 11) espone direttamente l'API di serializzazione strutturata , ma questa funzionalità è ancora contrassegnata come "sperimentale" e soggetta a modifiche o rimozione nelle versioni future. Se stai utilizzando una versione compatibile, la clonazione di un oggetto è semplice come:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Supporto diretto nei browser: forse alla fine? 😐
I browser attualmente non forniscono un'interfaccia diretta per l'algoritmo di clonazione strutturato, ma una structuredClone()
funzione globale è stata discussa in whatwg / html # 793 su GitHub . Come attualmente proposto, usarlo per la maggior parte degli scopi sarebbe semplice come:
const clone = structuredClone(original);
A meno che non vengano spediti, le implementazioni del clone strutturato dei browser sono esposte solo indirettamente.
Soluzione alternativa asincrona: utilizzabile. 😕
Il modo più basso per creare un clone strutturato con le API esistenti consiste nel pubblicare i dati attraverso una porta di un MessageChannels . L'altra porta emetterà un message
evento con un clone strutturato dell'allegato .data
. Sfortunatamente, l'ascolto di questi eventi è necessariamente asincrono e le alternative sincrone sono meno pratiche.
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);
Esempio di utilizzo:
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();
Soluzioni alternative sincrone: Terribile! 🤢
Non ci sono buone opzioni per la creazione di cloni strutturati in modo sincrono. Ecco un paio di hack poco pratici.
history.pushState()
ed history.replaceState()
entrambi creano un clone strutturato del loro primo argomento e assegnano quel valore a history.state
. Puoi usarlo per creare un clone strutturato di qualsiasi oggetto come questo:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
Esempio di utilizzo:
'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();
Sebbene sincrono, questo può essere estremamente lento. Presenta tutto il sovraccarico associato alla manipolazione della cronologia del browser. La chiamata ripetuta di questo metodo può causare la mancata risposta temporanea di Chrome.
Il Notification
costruttore crea un clone strutturato dei suoi dati associati. Tenta anche di mostrare all'utente una notifica del browser, ma ciò fallirà silenziosamente a meno che tu non abbia richiesto il permesso di notifica. Nel caso in cui tu abbia l'autorizzazione per altri scopi, chiuderemo immediatamente la notifica che abbiamo creato.
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Esempio di utilizzo:
'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();