Clonage structuré
La norme HTML comprend un algorithme de clonage / sérialisation structuré interne qui peut créer des clones profonds d'objets. Il est toujours limité à certains types intégrés, mais en plus des quelques types pris en charge par JSON, il prend également en charge les dates, les RegExps, les cartes, les ensembles, les objets blob, les listes de fichiers, les imagesDatas, les tableaux clairsemés, les tableaux typés et probablement plus à l'avenir . Il préserve également les références dans les données clonées, ce qui lui permet de prendre en charge des structures cycliques et récursives qui provoqueraient des erreurs pour JSON.
Prise en charge dans Node.js: expérimental 🙂
Le v8
module dans Node.js actuellement (à partir de Node 11) expose directement l'API de sérialisation structurée , mais cette fonctionnalité est toujours marquée comme "expérimentale" et sujette à modification ou suppression dans les futures versions. Si vous utilisez une version compatible, le clonage d'un objet est aussi simple que:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Prise en charge directe dans les navigateurs: peut-être éventuellement? 😐
Les navigateurs ne fournissent pas actuellement d'interface directe pour l'algorithme de clonage structuré, mais une structuredClone()
fonction globale a été discutée dans whatwg / html # 793 sur GitHub . Tel qu'il est actuellement proposé, son utilisation dans la plupart des cas serait aussi simple que:
const clone = structuredClone(original);
Sauf si cela est livré, les implémentations de clones structurés des navigateurs ne sont exposées qu'indirectement.
Solution de contournement asynchrone: utilisable. 😕
La manière la plus simple de créer un clone structuré avec des API existantes consiste à publier les données via un port d'un MessageChannels . L'autre port émettra un message
événement avec un clone structuré de l'attaché .data
. Malheureusement, l'écoute de ces événements est nécessairement asynchrone et les alternatives synchrones sont moins pratiques.
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);
Exemple d'utilisation:
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();
Solutions de contournement synchrones: horribles! 🤢
Il n'y a pas de bonnes options pour créer des clones structurés de manière synchrone. Voici quelques astuces peu pratiques à la place.
history.pushState()
et les history.replaceState()
deux créent un clone structuré de leur premier argument et attribuent cette valeur à history.state
. Vous pouvez l'utiliser pour créer un clone structuré de n'importe quel objet comme celui-ci:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
Exemple d'utilisation:
'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();
Bien que synchrone, cela peut être extrêmement lent. Il entraîne tous les frais généraux associés à la manipulation de l'historique du navigateur. L'appel répété de cette méthode peut entraîner une absence temporaire de réponse de Chrome.
Le Notification
constructeur crée un clone structuré de ses données associées. Il tente également d'afficher une notification du navigateur à l'utilisateur, mais cela échouera en silence, sauf si vous avez demandé l'autorisation de notification. Si vous avez l'autorisation à d'autres fins, nous fermerons immédiatement la notification que nous avons créée.
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Exemple d'utilisation:
'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();