Структуроване клонування
Стандарт HTML включає внутрішній структурований алгоритм клонування / серіалізації який може створювати глибокі клони об'єктів. Він все ще обмежений певними вбудованими типами, але крім кількох типів, що підтримуються JSON, він також підтримує дати, RegExps, карти, набори, блоби, списки файлів, ImageDatas, розріджені масиви, набрані масиви та, можливо, більше в майбутньому . Він також зберігає посилання в межах клонованих даних, що дозволяє йому підтримувати циклічні та рекурсивні структури, які можуть спричинити помилки для 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();