Clonagem Estruturada
O padrão HTML inclui um algoritmo interno de clonagem / serialização estruturado que pode criar clones profundos de objetos. Ele ainda é limitado a certos tipos internos, mas, além dos poucos tipos suportados pelo JSON, também suporta Datas, RegExps, Mapas, Conjuntos, Blobs, FileLists, ImageDatas, Matrizes esparsas, Matrizes digitadas e provavelmente mais no futuro . Ele também preserva referências nos dados clonados, permitindo suportar estruturas cíclicas e recursivas que causariam erros no JSON.
Suporte no Node.js: experimental 🙂
O v8
módulo no Node.js atualmente (a partir do Nó 11) expõe diretamente a API de serialização estruturada , mas essa funcionalidade ainda está marcada como "experimental" e está sujeita a alterações ou remoção em versões futuras. Se você estiver usando uma versão compatível, a clonagem de um objeto é tão simples quanto:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Suporte direto em navegadores: talvez eventualmente? 😐
Atualmente, os navegadores não fornecem uma interface direta para o algoritmo de clonagem estruturada, mas uma structuredClone()
função global foi discutida no whatwg / html # 793 no GitHub . Conforme proposto atualmente, usá-lo para a maioria dos propósitos seria tão simples quanto:
const clone = structuredClone(original);
A menos que isso seja enviado, as implementações de clones estruturados dos navegadores são expostas apenas indiretamente.
Solução alternativa assíncrona: utilizável. 😕
A maneira mais baixa de criar um clone estruturado com APIs existentes é postar os dados através de uma porta de um MessageChannels . A outra porta emitirá um message
evento com um clone estruturado do anexo .data
. Infelizmente, a escuta desses eventos é necessariamente assíncrona, e as alternativas síncronas são menos práticas.
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);
Exemplo 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();
Soluções síncronas: péssimas! 🤢
Não há boas opções para criar clones estruturados de forma síncrona. Aqui estão alguns truques impraticáveis.
history.pushState()
e history.replaceState()
ambos criam um clone estruturado de seu primeiro argumento e atribuem esse valor a history.state
. Você pode usar isso para criar um clone estruturado de qualquer objeto como este:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
Exemplo 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();
Embora síncrono, isso pode ser extremamente lento. Incorre em toda a sobrecarga associada à manipulação do histórico do navegador. Se você chamar esse método repetidamente, o Chrome ficará temporariamente sem resposta.
O Notification
construtor cria um clone estruturado de seus dados associados. Ele também tenta exibir uma notificação do navegador para o usuário, mas isso falhará silenciosamente, a menos que você tenha solicitado permissão de notificação. Caso você tenha permissão para outros fins, fecharemos imediatamente a notificação que criamos.
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Exemplo 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();