Я знаю, что WeakMap и WeakSet не являются итерируемыми по соображениям безопасности, то есть «чтобы злоумышленники не видели внутреннее поведение сборщика мусора », но тогда это означает, что вы не можете клонировать WeakMap или WeakSet так, как вы клонируете Map или Set, cloned_map = новая карта (существующая_карта), cloned_set = новый набор (существующий_набор).
Как клонировать WeakMap или WeakSet в Javascript? Под клонированием я подразумеваю создание другого WeakMap или WeakSet с такими же слабыми ссылками.
Их невозможно клонировать, и точка. Их цель — скрыть прямой доступ к ключам и значениям.
@vitaly-t Хотя в настоящее время это верно, невозможность доступа к ключам не может быть причиной невозможности клонирования (т. Е. Логически несвязанной).
Зачем кому-то клонировать структуру данных, о которой заботится сборщик мусора и где последняя, возможно, является одним большим аргументом в пользу существования WeakMap и WeakSet? Каждый клон дублирует ссылки, что может усложнить механизм очистки, чтобы выяснить, какие ссылки действительно «мертвые».
@PeterSeliger Под клонированием я подразумеваю клонирование экземпляра Weak* в другой экземпляр Weak*, а не что-то вроде .entries(), .keys() или .values(). Копирование слабых ссылок и сопоставлений без раскрытия поведения сборщика мусора. Все экземпляры Weak* управляются GC одинаково, и слабые ссылки мертвы, когда цели, на которые они указывают, мертвы; Я не совсем понимаю, как наличие нескольких слабых ссылок на одну и ту же цель, что уже возможно, усложняет что-либо.
Я сказал, что доступ к ключам и клонирование «логически не связаны», потому что клонирование может быть выполнено без предоставления пользователю доступа к ключам и значениям и раскрытия того, когда сборщик мусора собрал что. Эти два понятия просто разные, не связанные.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это можно сделать, полагая, что вы запускаете свой код до того, как что-то создаст слабую карту, отслеживая WeakMap.prototype.set и WeakMap.prototype.delete
Однако создание клона требует от меня сохранения собственного взгляда на вещи, так что это может привести к тому, что слабая карта никогда не будет собрана мусором ;-;
//the code you run first
(()=>{
let MAPS=new Map()
let DELETE=WeakMap.prototype.delete, SET=WeakMap.prototype.set
let BIND=Function.prototype.call.bind(Function.prototype.bind)
let APPLY=(FN,THIS,ARGS)=>BIND(Function.prototype.apply,FN)(THIS,ARGS)
WeakMap.prototype.set=
function(){
let theMap=MAPS.get(this)
if (!theMap){
theMap=new Map()
MAPS.set(this,theMap)
}
APPLY(theMap.set,theMap,arguments)
return APPLY(SET,this,arguments)
}
WeakMap.prototype.delete=
function(){
let theMap=MAPS.get(this)
if (!theMap){
theMap=new Map()
MAPS.set(this,theMap)
}
APPLY(theMap.delete,theMap,arguments)
return APPLY(DELETE,this,arguments)
}
function cloneWM(target){
let theClone=new WeakMap()
MAPS.get(target).forEach((value,key)=>{
APPLY(SET,theClone,[key,value])
})
return theClone
}
window.cloneWM=cloneWM
})()
//the example(go on devtools console to see it properly)
let w=new WeakMap()
w.set({a:1},'f')
w.set({b:2},'g')
w.set(window,'a')
w.delete(window)
console.info([w,cloneWM(w)])
console.info("go on devtools console to see it properly")тому, кто поставил минус 1 на мой пост, пока это не идеальный вопрос, это рабочий ответ
Если вы храните ключи в отдельном Map, то всегда будет строгая ссылка на этот ключ (т. е. он никогда не будет подвергаться сборке мусора), так что это в первую очередь противоречит цели использования WeakMap.
WeakMap/WeakSet нельзя «клонировать»?WeakMaps и WeakSets нельзя «клонировать» по той же причине, по которой вы не можете их повторять.
А именно, чтобы не выставлять задержку между моментом, когда ключ становится недоступным, и когда он удаляется из WeakMap / WeakSet. (Причина этого уже рассмотрена ваш связанный вопрос)
Спецификация языка ECMAScript 2023, объекты WeakMap 24.3
Реализация может устанавливать произвольно определяемую задержку между моментом, когда пара ключ/значение в WeakMap становится недоступной, и моментом, когда пара ключ/значение удаляется из WeakMap. Если бы эту задержку можно было наблюдать в программе ECMAScript, это было бы источником неопределенности, которая могла бы повлиять на выполнение программы. По этой причине реализация ECMAScript не должна предоставлять какие-либо средства для наблюдения за ключом WeakMap, которые не требуют от наблюдателя представления наблюдаемого ключа.
Подумайте, как new WeakMap(existingWeakMap) нужно реализовать.
Чтобы создать новый WeakMap из существующего, потребуется перебрать его элементы и скопировать их в новый.
И в зависимости от того, сколько элементов содержится в WeakMap, эта операция займет разное количество времени (копирование WeakMap со 100 000 записей займет намного больше времени, чем копирование без них).
И это дает вам вектор атаки: вы можете приблизительно оценить количество пар ключ-значение в WeakMap, измерив, сколько времени требуется для его клонирования.
Вот работающий фрагмент, который использует эту технику для угадывания количества записей в Map (можно было бы легко использовать против WeakMap, если бы его можно было клонировать):
Note that due to Spectre mitigations performance.now() in browsers is typically rounded, so a larger margin of error in the guesses should be expected.
function measureCloneTime(map) {
const begin = performance.now();
const cloneMap = new Map(map);
const end = performance.now();
return end-begin;
}
function measureAvgCloneTime(map, numSamples = 50) {
let timeSum = 0;
for(let i = 0; i < numSamples; i++) {
timeSum += measureCloneTime(map);
}
return timeSum / numSamples;
}
function makeMapOfSize(n) {
return new Map(Array(n).fill(null).map(() => [{}, {}]));
}
// prime JIT
for(let i = 0; i < 10000; i++) {
measureAvgCloneTime(makeMapOfSize(50));
}
const avgCloneTimes = [
{size: 2**6, time: measureAvgCloneTime(makeMapOfSize(2**6))},
{size: 2**7, time: measureAvgCloneTime(makeMapOfSize(2**7))},
{size: 2**8, time: measureAvgCloneTime(makeMapOfSize(2**8))},
{size: 2**9, time: measureAvgCloneTime(makeMapOfSize(2**9))},
{size: 2**10, time: measureAvgCloneTime(makeMapOfSize(2**10))},
{size: 2**11, time: measureAvgCloneTime(makeMapOfSize(2**11))},
{size: 2**12, time: measureAvgCloneTime(makeMapOfSize(2**12))},
{size: 2**13, time: measureAvgCloneTime(makeMapOfSize(2**13))},
{size: 2**14, time: measureAvgCloneTime(makeMapOfSize(2**14))},
];
function guessMapSizeBasedOnCloneSpeed(map) {
const cloneTime = measureAvgCloneTime(map);
let closestMatch = avgCloneTimes.find(e => e.time > cloneTime);
if (!closestMatch) {
closestMatch = avgCloneTimes[avgCloneTimes.length - 1];
}
const sizeGuess = Math.round(
(cloneTime / closestMatch.time) * closestMatch.size
);
console.info("Real Size: " + map.size + " - Guessed Size: " + sizeGuess);
}
guessMapSizeBasedOnCloneSpeed(makeMapOfSize(1000));
guessMapSizeBasedOnCloneSpeed(makeMapOfSize(4000));
guessMapSizeBasedOnCloneSpeed(makeMapOfSize(6000));
guessMapSizeBasedOnCloneSpeed(makeMapOfSize(10000));На моей машине (Ubuntu 20, Chrome 107) я получил следующий вывод (YMMV):
Real Size: 1000 - Guessed Size: 1037
Real Size: 4000 - Guessed Size: 3462
Real Size: 6000 - Guessed Size: 6329
Real Size: 10000 - Guessed Size: 9889
Как видите, невероятно легко угадать размер Map, просто клонировав его. (путем уточнения алгоритма/сбора большего количества выборок/использования более точного источника времени его можно было бы сделать еще более точным)
И именно поэтому вы не можете клонировать WeakMap/WeakSet.
Если вам нужен клонируемый/итерируемый WeakMap/WeakSet, вы можете создать свой собственный, используя WeakRef и FinalizationRegistry.
Вот пример того, как вы можете создать итерируемый WeakMap:
class IterableWeakMap {
#weakMap = new WeakMap();
#refSet = new Set();
#registry = new FinalizationRegistry(this.#cleanup.bind(this));
#cleanup(value) {
this.#refSet.delete(value);
}
constructor(iterable) {
if (iterable) {
for(const [key, value] of iterable) {
this.set(key, value);
}
}
}
get(key) {
return this.#weakMap.get(key)?.value;
}
has(key) {
return this.#weakMap.has(key);
}
set(key, value) {
let entry = this.#weakMap.get(key);
if (!entry) {
const ref = new WeakRef(key);
this.#registry.register(key, ref, key);
entry = {ref, value: null};
this.#weakMap.set(key, entry);
this.#refSet.add(ref);
}
entry.value = value;
return this;
}
delete(key) {
const entry = this.#weakMap.get(key);
if (!entry) {
return false;
}
this.#weakMap.delete(key);
this.#refSet.delete(entry.ref);
this.#registry.unregister(key);
return true;
}
clear() {
for(const ref of this.#refSet) {
const el = ref.deref();
if (el !== undefined) {
this.#registry.unregister(el);
}
}
this.#weakMap = new WeakMap();
this.#refSet.clear();
}
*entries() {
for(const ref of this.#refSet) {
const el = ref.deref();
if (el !== undefined) {
yield [el, this.#weakMap.get(el).value];
}
}
}
*keys() {
for(const ref of this.#refSet) {
const el = ref.deref();
if (el !== undefined) {
yield el;
}
}
}
*values() {
for(const ref of this.#refSet) {
const el = ref.deref();
if (el !== undefined) {
yield this.#weakMap.get(el).value;
}
}
}
forEach(callbackFn, thisArg) {
for(const [key, value] of this.entries()) {
callbackFn.call(thisArg, value, key, this);
}
}
[Symbol.iterator]() {
return this.entries();
}
get size() {
let size = 0;
for(const key of this.keys()) {
size++;
}
return size;
}
static get [Symbol.species]() {
return IterableWeakMap;
}
}
// Usage Example:
let foo = {foo: 42};
let bar = {bar: 42};
const map = new IterableWeakMap([
[foo, "foo"],
[bar, "bar"]
]);
const clonedMap = new IterableWeakMap(map);
console.info([...clonedMap.entries()]);«Клонирование не является защитой от атаки по времени и, таким образом, нарушает спецификацию ECMAScript».... Об этом я не подумал, и это довольно справедливое замечание! Спасибо за ваши идеи.
Может быть, вы могли бы использовать
Object.create?