Что такое WeakRef и финализаторы в ES2021 (ES12)

Я хочу понять, что такое и Finalizersв ES2021 с реальным простым примером и где их использовать.

Я знаю, WeakRefэто класс. Это позволит разработчикам создавать слабые ссылки на объекты, а Finalizer или FinalizationRegistry позволяет регистрировать функции обратного вызова, которые будут вызываться при сборке мусора

      const myWeakRef = new WeakRef({
  name: 'Cache',
  size: 'unlimited'
})

// Log the value of "myWeakRef":
console.log(myWeakRef.deref())

2 ответа

Как всегда, помогают документы MDN.

Объект WeakRef содержит слабую ссылку на объект, который называется его целью или референтом. Слабая ссылка на объект - это ссылка, которая не препятствует удалению объекта сборщиком мусора. Напротив, обычная (или сильная) ссылка сохраняет объект в памяти. Когда объект больше не имеет сильных ссылок на него, сборщик мусора движка JavaScript может уничтожить объект и освободить его память. Если это произойдет, вы больше не сможете получить объект из слабой ссылки.

Почти во всех остальных частях JS, если некоторый объект (A) содержит ссылку на другой объект (B), B не будет собираться мусором, пока A также не будет полностью собран. Например:

      // top level
const theA = {};
(() => {
  // private scope
  const theB = { foo: 'foo' };
  theA.obj = obj;
})();

В этой ситуации сборщик мусора никогда не будет (если только theA.obj переназначается), потому что theA на верхнем уровне содержит свойство, содержащее ссылку на theB; это сильная ссылка , которая предотвращает сборку мусора.

WeakRef, с другой стороны, предоставляет оболочку с доступом к объекту, не предотвращая сборку мусора этого объекта. Звонок deref()на WeakRef вернет вам объект, если он еще не был собран сборщиком мусора. Если это было GC'd, .deref() вернет `undefined.


FinalizationRegistry решает аналогичную проблему:

Объект FinalizationRegistry позволяет запрашивать обратный вызов, когда объект собирается сборщиком мусора.

Сначала вы определяете реестр с помощью обратного вызова, который хотите запустить, а затем вызываете реестр с объектом, за которым хотите наблюдать. Это позволит вам точно узнать , когда что-то будет собрано сборщиком мусора. Например, следующее будет регистрировать Just got GCd! однажды obj восстанавливается:

      console.log('script starting...');

const r = new FinalizationRegistry(() => {
  console.log('Just got GCd!');
});
(() => {
  // private closure
  const obj = {};
  r.register(obj);
})();

Вы также можете передать значение при вызове .register который передается в обратный вызов, когда объект собирается.

      new FinalizationRegistry((val) => {
  console.log(val);
});
      r.register(obj, 'the object named "obj"')

будет регистрировать the object named "obj" он получает GC'd.

При этом потребность в этих инструментах возникает редко . Как говорит MDN:

Правильное использование FinalizationRegistry требует тщательного обдумывания, и по возможности его лучше избегать. Также важно не полагаться на какое-либо конкретное поведение, не гарантированное спецификацией. Когда, как и происходит ли сборка мусора, зависит от реализации того или иного механизма JavaScript. Любое поведение, которое вы наблюдаете в одном движке, может отличаться в другом движке, в другой версии того же движка или даже в несколько иной ситуации с той же версией того же самого движка. Сборка мусора - сложная проблема, которую разработчики движка JavaScript постоянно совершенствуют и совершенствуют свои решения.

Лучше позволить самому движку заниматься сборкой мусора автоматически, когда это возможно, если у вас нет действительно веских причин заботиться об этом самостоятельно.

Основное использование слабых ссылок - реализация кешей или сопоставлений с большими объектами. Во многих сценариях мы не хотим сохранять много памяти в течение длительного времени, сохраняя этот редко используемый кеш или сопоставления. Мы можем разрешить сборку мусора для памяти в ближайшее время, а позже, если она нам снова понадобится, мы можем сгенерировать новый кеш. Если переменная больше недоступна, сборщик мусора JavaScript автоматически удаляет ее.

      const callback = () => {
  const aBigObj = {
    name: "Hello world"
  };
  console.log(aBigObj);
}

(async function(){
  await new Promise((resolve) => {
    setTimeout(() => {
      callback();
      resolve();
    }, 2000);
  });
})();

При выполнении вышеуказанного кода он печатает «Helloworld» через 2 секунды. В зависимости от того, как мы используем функцию callback (), возможно, aBigObj хранится в памяти навсегда.

Давайте сделаем aBigObj слабой ссылкой.

      const callback = () => {
  const aBigObj = new WeakRef({    name: "Hello world"  });  console.log(aBigObj.deref().name);}

(async function(){
  await new Promise((resolve) => {
    setTimeout(() => {
      callback(); // Guaranteed to print "Hello world"
      resolve();
    }, 2000);
  });

  await new Promise((resolve) => {
    setTimeout(() => {
      callback(); // No Gaurantee that "Hello world" is printed
      resolve();
    }, 5000);
  });
})();

Первый setTimeout() обязательно напечатает значение имени. Это гарантируется на первом этапе цикла обработки событий после создания слабой ссылки.

Но нет гарантии, что второй setTimeout() напечатает «Backbencher». Он мог быть удален сборщиком мусора. Поскольку сборка мусора в разных браузерах работает по-разному, мы не можем гарантировать результат. Вот почему мы используем WeakRef в таких ситуациях, как управление кешем.

Дополнительная информация...

Другие вопросы по тегам