Деструктор класса ECMAScript 6

Я знаю, что ECMAScript 6 имеет конструкторы, но есть ли такая вещь, как деструкторы для ECMAScript 6?

Например, если я регистрирую некоторые методы моего объекта в качестве прослушивателей событий в конструкторе, я хочу удалить их при удалении моего объекта.

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

Но я надеялся, что в ECMAScript 6 есть что-то нативное, которое будет вызываться прямо перед сборкой мусора.

Если такого механизма не существует, что такое шаблон / соглашение для таких проблем?

11 ответов

Решение

Есть ли такая вещь, как деструкторы для ECMAScript 6?

Нет. EcmaScript 6 вообще не определяет семантику сборки мусора [1], поэтому нет ничего похожего на "уничтожение".

Если я регистрирую некоторые методы моего объекта в качестве прослушивателей событий в конструкторе, я хочу удалить их, когда мой объект удален

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

1): Ну, есть начало со спецификацией WeakMap а также WeakSet объекты. Однако настоящие слабые ссылки все еще находятся в стадии разработки [1] [2].

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

Спасибо вам, ребята. Но что будет хорошим соглашением, если в ECMAScript нет деструкторов? Должен ли я создать метод с именем деструктор и вызвать его вручную, когда я закончу с объектом? Любая другая идея?

Если вы хотите сообщить своему объекту, что с ним теперь покончено, и он должен специально освободить всех прослушивателей событий, которые у него есть, тогда вы можете просто создать обычный метод для этого. Вы можете назвать метод что-то вроде release() или же deregister() или же unhook() или что-нибудь в этом роде. Идея состоит в том, что вы говорите объекту отключить себя от всего, к чему он подключен (отменить регистрацию прослушивателей событий, очистить ссылки на внешние объекты и т. Д.). Вам нужно будет позвонить вручную в соответствующее время.

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

ES6 действительно имеет слабые карты и слабый набор, которые являются способами отслеживания набора объектов, которые все еще живы, не влияя на то, когда их можно собирать, но не предоставляют никаких уведомлений, когда они собираются. В какой-то момент они просто исчезают из слабой карты или слабого набора (когда они GCed).


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

Я полагаю, что есть возможность weakListener() это не помешало бы сборке мусора, но такой вещи тоже не существует.


К вашему сведению, вот еще один важный вопрос: почему парадигма деструктора объектов в языках с мусорным сбором повсеместно отсутствует?, Это обсуждение охватывает шаблоны дизайна финализатора, деструктора и диспозитора. Я нашел полезным увидеть разницу между тремя.

Вы должны вручную "уничтожить" объекты в JS. Создание функции уничтожения распространено в JS. В других языках это можно назвать свободным, выпуском, удалением, закрытием и т. Д. По моему опыту, это, как правило, уничтожение, которое отцепит внутренние ссылки, события и, возможно, также распространит вызовы уничтожения на дочерние объекты.

Слабые карты в значительной степени бесполезны, так как они не могут быть повторены, и это, вероятно, не будет доступно до ECMA 7, если вообще будет. Все, что позволяют WeakMaps - это иметь невидимые свойства, отсоединенные от самого объекта, за исключением поиска по ссылке на объект и GC, чтобы они не мешали ему. Это может быть полезно для кэширования, расширения и работы с множественностью, но на самом деле это не помогает в управлении памятью для наблюдаемых и наблюдателей. WeakSet является подмножеством WeakMap (подобно WeakMap со значением по умолчанию, равным логическому true).

Существуют различные аргументы относительно того, использовать ли различные реализации слабых ссылок для этого или деструкторов. У обоих есть потенциальные проблемы, и деструкторы более ограничены.

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

Конкретный случай, который я начал изучать, заключается в том, что у меня есть экземпляр класса A, который принимает класс B в конструкторе, а затем создает экземпляр класса C, который слушает B. Я всегда держу экземпляр B где-то высоко над собой. ИИ иногда выбрасывают, создают новые, создают много и т. Д. В этом случае Деструктор действительно работал бы для меня, но с неприятным побочным эффектом, который в родителе, если я передал экземпляр C, но удалил все ссылки A, затем C и Привязка B будет нарушена (у C снизу есть земля).

В JS отсутствие автоматического решения является болезненным, но я не думаю, что это легко решаемо. Рассмотрим эти классы (псевдо):

function Filter(stream) {
    stream.on('data', function() {
        this.emit('data', data.toString().replace('somenoise', '')); // Pretend chunks/multibyte are not a problem.
    });
}
Filter.prototype.__proto__ = EventEmitter.prototype;
function View(df, stream) {
    df.on('data', function(data) {
        stream.write(data.toUpper()); // Shout.
    });
}

Кстати, трудно заставить вещи работать без анонимных / уникальных функций, которые будут рассмотрены позже.

В обычном случае создание экземпляра было бы так (псевдо):

var df = new Filter(stdin),
    v1 = new View(df, stdout),
    v2 = new View(df, stderr);

Для GC это обычно вы устанавливаете в NULL, но это не сработает, потому что они создали дерево с stdin в корне. Это в основном то, что делают системы событий. Вы даете родителю ребенка, ребенок добавляет себя к родителю, а затем может поддерживать или не поддерживать ссылку на родителя. Дерево - простой пример, но на самом деле вы можете столкнуться со сложными графиками, хотя и редко.

В этом случае Filter добавляет ссылку на себя в stdin в форме анонимной функции, которая косвенно ссылается на Filter by scope. Ссылки на предметную область - это то, что нужно знать, и это может быть довольно сложным. Мощный сборщик мусора может сделать несколько интересных вещей, чтобы разделить элементы в переменных области, но это уже другая тема. Важно понимать, что когда вы создаете анонимную функцию и добавляете ее к чему-либо в качестве прослушивателя ab observable, observable будет сохранять ссылку на функцию и все, на что ссылается функция в областях над ней (что было определено в) также будет поддерживаться. Представления делают то же самое, но после выполнения своих конструкторов дети не сохраняют ссылку на своих родителей.

Если я установлю любое или все вары, объявленные выше, равными нулю, это ничего не изменит (аналогично, когда он завершит эту "основную" область видимости). Они по-прежнему будут активны и передают данные из стандартного ввода в стандартный вывод и стандартный вывод.

Если бы я установил их все в null, было бы невозможно удалить их или GCed без очистки событий на stdin или установки stdin на null (при условии, что он может быть освобожден таким образом). По сути, у вас возникает утечка памяти с фактически потерянными объектами, если остальная часть кода нуждается в stdin и имеет другие важные события, запрещающие вам делать вышеупомянутые.

Чтобы избавиться от df, v1 и v2, мне нужно вызвать метод уничтожения для каждого из них. С точки зрения реализации это означает, что оба метода Filter и View должны сохранять ссылку на анонимную функцию прослушивателя, которую они создают, а также наблюдаемую и передавать ее в removeListener.

Напомним, что в качестве альтернативы у вас может быть obserable, который возвращает индекс для отслеживания слушателей, чтобы вы могли добавлять прототипированные функции, которые, по крайней мере, на мой взгляд, должны быть намного лучше по производительности и памяти. Вы все равно должны отслеживать возвращаемый идентификатор и передавать свой объект, чтобы убедиться, что слушатель привязан к нему при вызове.

Функция уничтожения добавляет несколько болей. Во-первых, я должен позвонить и освободить ссылку:

df.destroy();
v1.destroy();
v2.destroy();
df = v1 = v2 = null;

Это небольшое раздражение, так как это немного больше кода, но это не настоящая проблема. Когда я передаю эти ссылки многим объектам. В этом случае, когда именно вы называете уничтожить? Вы не можете просто передать их другим объектам. В итоге вы получите цепочки разрушений и ручную реализацию отслеживания либо через поток программ, либо с помощью других средств. Ты не можешь выстрелить и забыть.

Примером такого рода проблем является то, что если я решу, что View также вызовет destroy на df, когда он будет уничтожен. Если v2 все еще существует, уничтожение df сломает его, поэтому разрушение нельзя просто передать df. Вместо этого, когда v1 использует df для его использования, он должен затем сказать df, что он используется, что вызовет некоторый счетчик или аналогично df. Функция разрушения в df будет меньше, чем counter, и уничтожит только если она будет равна 0. Подобные вещи увеличивают сложность и добавляют много неправильного, наиболее очевидным из которых является уничтожение чего-либо, в то время как где-то есть ссылка, будут использоваться и циклические ссылки (на данный момент это уже не случай управления счетчиком, а карта ссылок на объекты). Когда вы думаете о внедрении своих собственных счетчиков ссылок, MM и т. Д. В JS, то это, вероятно, недостаточно.

Если бы WeakSets были итеративными, это можно было бы использовать:

function Observable() {
    this.events = {open: new WeakSet(), close: new WeakSet()};
}
Observable.prototype.on = function(type, f) {
    this.events[type].add(f);
};
Observable.prototype.emit = function(type, ...args) {
    this.events[type].forEach(f => f(...args));
};
Observable.prototype.off = function(type, f) {
    this.events[type].delete(f);
};

В этом случае класс-владелец также должен хранить ссылку на токен f, иначе он будет иметь значение poof.

Если вместо EventListener будут использоваться Observable, то управление памятью будет автоматическим в отношении слушателей событий.

Вместо вызова уничтожения для каждого объекта этого будет достаточно, чтобы полностью удалить их:

df = v1 = v2 = null;

Если вы не установите для df значение null, оно все равно будет существовать, но v1 и v2 будут автоматически отключены.

Однако у этого подхода есть две проблемы.

Проблема первая в том, что это добавляет новую сложность. Иногда люди на самом деле не хотят такого поведения. Я мог бы создать очень большую цепочку объектов, связанных друг с другом событиями, а не сдерживанием (ссылки в областях конструктора или свойствах объекта). В конце концов дерево, и мне нужно будет только обойти корень и беспокоиться об этом. Освободив рут, удобно освободить всю вещь. Оба поведения в зависимости от стиля кодирования и т. Д. Полезны, и при создании многократно используемых объектов будет трудно либо узнать, что люди хотят, что они сделали, что вы сделали, и боль, чтобы обойти то, что было сделано. Если я использую Observable вместо EventListener, то либо df нужно будет ссылаться на v1 и v2, либо мне придется передать их все, если я хочу передать право собственности на ссылку на что-то другое вне области. Слабая ссылка, как вещь, могла бы немного смягчить проблему, передав контроль от Observable наблюдателю, но не разрешила бы его полностью (и нуждается в проверке каждого выброса или события на себе). Эта проблема может быть исправлена, я полагаю, если поведение применяется только к изолированным графам, что серьезно усложнит сборщик мусора и не будет применяться к случаям, когда есть ссылки вне графика, которые на практике являются noops (только потребляют циклы ЦП, не внося изменений).

Проблема вторая в том, что в некоторых случаях это либо непредсказуемо, либо вынуждает механизм JS пересекать граф GC для тех объектов по требованию, которые могут оказать ужасное влияние на производительность (хотя, если он умный, он может избежать этого для каждого члена, выполняя Слабый цикл вместо). GC может никогда не запуститься, если использование памяти не достигает определенного порога и объект с его событиями не будет удален. Если я установлю v1 на ноль, он все равно может перейти на стандартный вывод навсегда. Даже если он получит GCed, это будет произвольно, он может продолжать ретранслировать на стандартный вывод в течение любого промежутка времени (1 строка, 10 строк, 2,5 строки и т. Д.).

Причина, по которой WeakMap перестает заботиться о GC, когда он не повторяется, заключается в том, что для доступа к объекту вы все равно должны иметь ссылку на него, чтобы либо он не был GCed, либо не был добавлен на карту.

Я не уверен, что я думаю об этом. Вы как бы ломаете управление памятью, чтобы исправить это с помощью итеративного подхода WeakMap. Вторая проблема может существовать и для деструкторов.

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

Есть еще одно решение, которое довольно элегантно, но опять же имеет некоторые потенциально серьезные проблемы. Если у вас есть класс, который расширяет наблюдаемый класс, вы можете переопределить функции события. Добавляйте свои события в другие наблюдаемые только тогда, когда события добавляются к вам. Когда все события удалены от вас, тогда удалите свои события от детей. Вы также можете создать класс для расширения вашего наблюдаемого класса, чтобы сделать это для вас. Такой класс мог бы предоставлять хуки для пустых и непустых, так что, поскольку вы будете сами наблюдать. Этот подход не плох, но также имеет зависания. Существует увеличение сложности, а также снижение производительности. Вам нужно будет сохранить ссылку на объект, который вы наблюдаете. Критически, это также не будет работать для листьев, но по крайней мере промежуточные продукты будут самоуничтожаться, если вы уничтожите лист. Это похоже на цепочку разрушений, но скрыто за вызовами, которые вам уже приходится связывать. С этим связана большая проблема производительности, заключающаяся в том, что вам, возможно, придется заново инициализировать внутренние данные из Observable каждый раз, когда ваш класс становится активным. Если этот процесс занимает очень много времени, у вас могут быть проблемы.

Если бы вы могли перебирать WeakMap, вы могли бы комбинировать вещи (переключаться на Weak, когда нет событий, Strong, когда события), но все, что действительно делает, - это ставит проблему производительности кому-то еще.

Есть также непосредственные неприятности с повторяемым WeakMap, когда дело доходит до поведения. Ранее я кратко упомянул о функциях, имеющих ссылки на область действия и резьбу. Если я создаю экземпляр дочернего объекта, который в конструкторе, который подключает прослушиватель 'console.log(param)' к родительскому элементу и не может сохранить родительский элемент, тогда, когда я удаляю все ссылки на дочерний элемент, он может быть полностью освобожден как анонимная функция, добавленная к родитель ничего не ссылается на ребенка. Это оставляет вопрос о том, что делать с parent.weakmap.add(child, (param) => console.log(param)). Насколько мне известно, ключ является слабым, но не является значением, поэтому уязвимыми является файл map.add (объект, объект). Это то, что мне нужно переоценить, хотя. Для меня это выглядит как утечка памяти, если я избавляюсь от всех других ссылок на объекты, но я подозреваю, что на самом деле это удается, рассматривая это как циклическую ссылку. Либо анонимная функция поддерживает неявную ссылку на объекты, являющиеся результатом родительских областей, из-за непоследовательности, тратящей много памяти, либо ваше поведение меняется в зависимости от обстоятельств, которые трудно предсказать или управлять ими. Я думаю, что первое на самом деле невозможно. В последнем случае, если у меня есть метод класса, который просто берет объект и добавляет console.log, он освобождается, когда я очищаю ссылки на класс, даже если я вернул функцию и сохранил ссылку. Чтобы быть справедливым, этот конкретный сценарий редко требуется законно, но в конце концов кто-то найдет угол и будет просить HalfWeakMap, который является итеративным (свободным по ключу и освобожденным ссылкам значений), но который также непредсказуем (obj = null, магически заканчивающийся IO, f = нулевой магически заканчивающийся ввод-вывод, оба выполнимо на невероятных расстояниях).

Ну вот. Subscribe/Publishобъект будет unsubscribeфункция обратного вызова автоматически, если она выходит за рамки и получает сборку мусора.

      const createWeakPublisher = () => {
  const weakSet = new WeakSet();
  const subscriptions = new Set();

  return {
    subscribe(callback) {
      if (!weakSet.has(callback)) {
        weakSet.add(callback);
        subscriptions.add(new WeakRef(callback));
      }

      return callback;
    },

    publish() {
      for (const weakRef of subscriptions) {
        const callback = weakRef.deref();
        console.log(callback?.toString());

        if (callback) callback();
        else subscriptions.delete(weakRef);
      }
    },
  };
};

Хотя это может произойти не сразу после того, как callback-функция выйдет из области видимости, а может и не произойти вовсе. См . документацию по weakRef для более подробной информации. Но это работает как шарм для моего варианта использования.

Вы также можете проверить API FinalizationRegistry для другого подхода.

Если такого механизма нет, каков шаблон / соглашение для таких проблем?

Термин "очистка" может быть более подходящим, но для соответствия OP будет использоваться "деструктор".

Предположим, вы пишете какой-нибудь javascript полностью с помощью функций и var. Тогда вы можете использовать схему написания всехfunctionкод в рамках try/catch/finallyрешетка. Вfinally выполнить код уничтожения.

Вместо стиля C++ написания классов объектов с неопределенным временем жизни с последующим указанием времени жизни произвольными областями и неявным вызовом ~() в конце области действия (~() является деструктором в C++), в этом шаблоне javascript объект - это функция, область действия - это область действия функции, а деструктор - это finally блок.

Если вы сейчас думаете, что этот паттерн изначально ошибочен, потому что try/catch/finallyне включает асинхронное выполнение, которое необходимо для javascript, значит, вы правы. К счастью, с 2018 года вспомогательный объект асинхронного программированияPromise имеет функцию прототипа finally добавлен к уже существующим resolve а также catchфункции прототипа. Это означает, что асинхронные области, требующие деструкторов, могут быть записаны с помощьюPromise объект, используя finallyкак деструктор. Кроме того, вы можете использоватьtry/catch/finally в async function вызов Promiseс или без await, но следует помнить, что Promises, вызываемые без ожидания, будут выполняться асинхронно за пределами области видимости и, таким образом, обработать код десктруктора в финальном then.

В следующем коде PromiseA а также PromiseB это некоторые устаревшие обещания уровня API, у которых нет finally указаны аргументы функции. PromiseC ДЕЙСТВИТЕЛЬНО определен аргумент finally.

async function afunc(a,b){
    try {
        function resolveB(r){ ... }
        function catchB(e){ ... }
        function cleanupB(){ ... }
        function resolveC(r){ ... }
        function catchC(e){ ... }
        function cleanupC(){ ... }
        ...
        // PromiseA preced by await sp will finish before finally block.  
        // If no rush then safe to handle PromiseA cleanup in finally block 
        var x = await PromiseA(a);
        // PromiseB,PromiseC not preceded by await - will execute asynchronously
        // so might finish after finally block so we must provide 
        // explicit cleanup (if necessary)
        PromiseB(b).then(resolveB,catchB).then(cleanupB,cleanupB);
        PromiseC(c).then(resolveC,catchC,cleanupC);
    }
    catch(e) { ... }
    finally { /* scope destructor/cleanup code here */ }
}

Я не защищаю, чтобы каждый объект в javascript был написан как функция. Вместо этого рассмотрим случай, когда у вас идентифицирована область видимости, которая действительно "хочет", чтобы деструктор вызывался в конце своей жизни. Сформулируйте эту область действия как объект функции, используя шаблонfinally блок (или finallyфункция в случае асинхронной области видимости) в качестве деструктора. Вполне вероятно, что формулировка этого функционального объекта устранила необходимость в нефункциональном классе, который в противном случае был бы написан - никакого дополнительного кода не требовалось, выравнивание области видимости и класса могло бы быть даже чище.

Примечание: как писали другие, не следует путать деструкторы и сборку мусора. Как оказалось, деструкторы C++ часто или в основном связаны с ручной сборкой мусора, но не исключительно. В Javascript нет необходимости в ручной сборке мусора, но конец срока службы асинхронной области часто является местом для (де) регистрации слушателей событий и т. Д.

"Деструктор даже не поможет вам в этом. Сами слушатели событий по-прежнему ссылаются на ваш объект, поэтому он не сможет собирать мусор до того, как он будет незарегистрирован".

Не так. Цель деструктора - разрешить элементу, который зарегистрировал слушателей, отменить их регистрацию. Например, в Angular, когда контроллер уничтожается, он может прослушивать событие уничтожения и отвечать на него. Это не то же самое, что автоматически вызывать деструктор, но он близок и дает нам возможность удалить прослушиватели, которые были установлены при инициализации контроллера.

// Initialize the controller
        function initialize() {

            // Set event listeners, hanging onto the returned listener removal functions
            $scope.listenerCleanup = [];
            $scope.listenerCleanup.push( $scope.$on( EVENTS.DESTROY, instance.onDestroy) );
            $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.CREATE_USER.SUCCESS, instance.onCreateUserResponse ) );
            $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.CREATE_USER.FAILURE, instance.onCreateUserResponse ) );
            $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.RESET_PW.SUCCESS, instance.onSendResetEmailResponse ) );
            $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.RESET_PW.FAILURE, instance.onSendResetEmailResponse ) );
            $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.CHANGE_PW.SUCCESS, instance.onChangePasswordResponse ) );
            $scope.listenerCleanup.push( $scope.$on( AUTH_SERVICE_RESPONSES.CHANGE_PW.FAILURE, instance.onChangePasswordResponse ) );
            $scope.listenerCleanup.push( $scope.$on( ACCOUNT_SERVICE_RESPONSES.CREATE_PROFILE.SUCCESS, instance.onCreateProfileResponse ) );
            $scope.listenerCleanup.push( $scope.$on( ACCOUNT_SERVICE_RESPONSES.CREATE_PROFILE.FAILURE, instance.onCreateProfileResponse ) );
            };
        }

        /**
         * Remove event listeners when the controller is destroyed
         */
        function onDestroy(){
            var i, removeListener;
            for (i=0; i < $scope.listenerCleanup.length; i++){
                removeListener = $scope.listenerCleanup[i];
                removeListener();
            }
        }

В других ответах уже подробно объяснялось, что деструктора нет. Но ваша настоящая цель, похоже, связана с событием. У вас есть объект, который связан с некоторым событием, и вы хотите, чтобы это соединение отключалось автоматически при сборке мусора. Но этого не произойдет, потому что сама подписка на событие ссылается на функцию прослушивателя. Что ж, ЕСЛИ вы не используете этот изящный новый материал WeakRef .

Вот пример:

      <!DOCTYPE html>
<html>
  <body>
    <button onclick="subscribe()">Subscribe</button>
    <button id="emitter">Emit</button>
    <button onclick="free()">Free</button>
    <script>

    const emitter = document.getElementById("emitter");
    let listener = null;

    function addWeakEventListener(element, event, callback) {
        // Weakrefs only can store objects, so we put the callback into an object
        const weakRef = new WeakRef({ callback });
        const listener = () => {
            const obj = weakRef.deref();
            if (obj == null) {
                console.log("Removing garbage collected event listener");
                element.removeEventListener(event, listener);
            } else {
                obj.callback();
            }
        };
        element.addEventListener(event, listener);
    }

    function subscribe() {
        listener = () => console.log("Event fired!");
        addWeakEventListener(emitter, "click", listener);
        console.log("Listener created and subscribed to emitter");
    }

    function free() {
        listener = null;
        console.log("Reference cleared. Now force garbage collection in dev console or wait some time before clicking Emit again.");
    }

    </script>
  </body>
</html>

(JSFiddle)

Нажатие кнопки « Подписаться» создает новую функцию прослушивателя и регистрирует ее в событии нажатия кнопки « Emit» . Поэтому нажатие кнопки « Emit» после этого выводит сообщение на консоль. Теперь нажмите кнопку Free , которая просто устанавливает для переменной прослушивателя значение null, чтобы сборщик мусора мог удалить прослушиватель. Подождите некоторое время или принудительно выполните сбор мусора в консоли разработчика, а затем снова нажмите кнопку Emit . Функция прослушивателя оболочки теперь видит, что фактического слушателя (заключенного в WeakRef) больше нет, и затем отписывается от кнопки.

WeakRefs довольно мощные, но учтите, что нет никакой гарантии, когда и когда ваш материал будет собран сборщиком мусора.

TC39 наконец достиг стадии 3 (для первых пользователей).

Теперь вы можете добавить[Symbol.dispose]к вашим объектам и используйте новое ключевое словоusingчтобы позволить ему вызвать, как только ваш объект выйдет из области видимости.

Существует даже асинхронный вариант с этим символом:[Symbol.asyncDispose]и ключевое слово:await using.

Javascript не имеет деструктур, как C++. Вместо этого для управления ресурсами следует использовать альтернативные шаблоны проектирования. Вот пара примеров:

Вы можете ограничить пользователей использованием экземпляра на время обратного вызова, после чего он будет автоматически очищен. (Этот шаблон похож на любимый оператор «with» в Python)

      connectToDatabase(async db => {
  const resource = await db.doSomeRequest()
  await useResource(resource)
}) // The db connection is closed once the callback ends

Если приведенный выше пример является слишком ограничительным, другой альтернативой является создание явных функций очистки.

      const db = makeDatabaseConnection()

const resource = await db.doSomeRequest()
updatePageWithResource(resource)

pageChangeEvent.addListener(() => {
  db.destroy()
})

Ответ на поставленный в заголовке вопросFinalizationRegistry, доступный начиная с Firefox 79 (июнь 2020 г.), Chrome 84 и его производных (июль 2020 г.), Safari 14.1 (апрель 2021 г.) и Node 14.6.0 (июль 2020 г.)… однако собственный деструктор JS, вероятно, не является правильным решением для вашего прецедент .

      function create_eval_worker(f) {
    let src_worker_blob = new Blob([f.toString()], {type: 'application/javascript'});
    let src_worker_url = URL.createObjectURL(src_worker_blob);

    async function g() {
        let w = new Worker(src_worker_url);
        …
    }

    // Run URL.revokeObjectURL(src_worker_url) as a destructor of g
    let registry = new FinalizationRegistry(u => URL.revokeObjectURL(u));
    registry.register(g, src_worker_url);

    return g;
    }
}

Предостережение:

По возможности избегайте

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

Разработчики не должны полагаться на обратные вызовы очистки для основной логики программы. Обратные вызовы очистки могут быть полезны для сокращения использования памяти в ходе выполнения программы, но вряд ли будут полезны в других случаях.

Соответствующая реализация JavaScript, даже та, которая выполняет сборку мусора, не требуется для вызова обратных вызовов очистки. Когда и произойдет ли это, полностью зависит от реализации движка JavaScript. Когда зарегистрированный объект освобождается, любые обратные вызовы очистки для него могут быть вызваны тогда, через некоторое время или вообще не вызываться.

– Сеть разработчиков Mozilla

В будущем появится функция управления ресурсами, которая может оказаться полезной в этом случае. Дополнительную информацию об этом можно найти на странице ECMAScript Explicit Resource Management.

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