Структурированное клонирование
Стандарт HTML включает в себя внутренний структурированный алгоритм клонирования / сериализации , который может создавать глубокие клоны объектов. Он по-прежнему ограничен определенными встроенными типами, но в дополнение к нескольким типам, поддерживаемым JSON, он также поддерживает Dates, RegExps, Карты, Наборы, BLOB-объекты, FileLists, ImageDatas, разреженные массивы, Typed Arrays и, возможно, больше в будущем. , Он также сохраняет ссылки в клонированных данных, что позволяет поддерживать циклические и рекурсивные структуры, которые могут вызвать ошибки для JSON.
Поддержка в Node.js: экспериментальная ?
Модуль v8
в Node.js в настоящее время (по состоянию на Узле 11) напрямую предоставляет структурированный API сериализации , но эта функциональность по-прежнему помечена как «экспериментальная» и может быть изменена или удалена в будущие версии. Если вы используете совместимую версию, клонирование объекта так же просто, как:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Прямая поддержка в браузерах: возможно, в конце концов? ?
Браузеры в настоящее время не предоставляют прямой интерфейс для алгоритма структурированного клонирования, но глобальная функция structuredClone()
обсуждалась в whatwg / html # 793 на GitHub . Как предлагается в настоящее время, использовать его для большинства целей так же просто, как:
const clone = structuredClone(original);
Если это не сделано, реализации структурированных клонов в браузерах предоставляются только косвенно.
Асинхронный обходной путь: можно использовать. ?
Более простой способ создания структурированного клона с существующими API-интерфейсами - это отправка данных через один порт MessageChannels . Другой порт будет генерировать событие message
со структурированным клоном присоединенного .data
. К сожалению, прослушивание этих событий обязательно асинхронно, а синхронные альтернативы менее практичны.
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);
Пример использования:
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();
Синхронные обходные пути: Ужасно! ?
Нет хороших вариантов для синхронного создания структурированных клонов. Вот пара непрактичных взломов.
history.pushState()
и history.replaceState()
оба создают структурированный клон своего первого аргумента и присваивают это значение history.state
. Вы можете использовать это для создания структурированного клона любого объекта, подобного этому:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
Пример использования:
'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();
Хотя синхронно, это может быть очень медленно. Это влечет за собой все накладные расходы, связанные с манипулированием историей браузера. Повторный вызов этого метода может привести к тому, что Chrome временно не будет отвечать на запросы.
Конструктор Notification
создает структурированный клон связанных с ним данных. Он также пытается отобразить уведомление в браузере для пользователя, но это молча завершится ошибкой, если вы не запросили разрешение на уведомление. Если у вас есть разрешение на другие цели, мы немедленно закроем созданное нами уведомление.
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
Пример использования:
'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();