Distrugătorul clasei ECMAScript 6 (Programare, Javascript, Ecmascript 6)

AlexStack a intrebat.
a intrebat.

Știu că ECMAScript 6 are constructori, dar există destructori pentru ECMAScript 6?

De exemplu, dacă înregistrez unele dintre metodele obiectului meu ca ascultători de evenimente în constructor, vreau să le elimin atunci când obiectul meu este șters.

O soluție ar fi convenția de a crea o clasă desctructor pentru fiecare clasă care are nevoie de acest tip de comportament și să o apelăm manual. Acest lucru va elimina referințele la gestionarii de evenimente și, prin urmare, obiectul meu va fi cu adevărat pregătit pentru colectarea gunoiului. În caz contrar, va rămâne în memorie din cauza acelor metode.

Dar speram dacă ECMAScript 6 are ceva nativ care va fi apelat chiar înainte ca obiectul să fie colectat la gunoi.

Dacă nu există un astfel de mecanism, care este un model/convenție pentru astfel de probleme?

Comentarii

  • Nu aș spune că un destructor ar fi locul potrivit pentru acest lucru. Colectorii de gunoi nu colectează memoria atunci când te aștepți la ea, astfel, cred că un fel de eliminare manuală este încă necesară. –  > Por Matías Fidemraizer.
  • o posibilă duplicare a echivalentului lui Javascript pentru destruct în modelul de obiect –  > Por Legende.
  • Dacă aveți ascultători de evenimente, obiectul dvs. nu poate fi GC’d până când aceștia nu dispar. Nu există niciun scenariu în care o astfel de caracteristică ar fi utilă. –  > Por SLaks.
  • vă mulțumesc, băieți. Dar care ar fi o convenție bună dacă ECMAScript nu are destructori? Ar trebui să creez o metodă numită destructor și să o chem manual când termin cu obiectul? Orice altă idee? –  > Por AlexStack.
  • rupeți secțiunea de legare a event-handler-ului în două metode: this.subscribe() și this.unsubscribe() –  > Por dandavis.
5 răspunsuri
Bergi

Există așa ceva ca destructori pentru ECMAScript 6?

Nu. EcmaScript 6 nu specifică deloc o semantică de colectare a gunoiului[1], așa că nu există nici un fel de „distrugere”.

Dacă înregistrez unele dintre metodele obiectului meu ca ascultători de evenimente în constructor, vreau să le elimin atunci când obiectul meu este șters

Un destructor nici măcar nu v-ar ajuta aici. Ascultătorii de evenimente în sine sunt cei care încă fac referire la obiectul dvs., astfel încât acesta nu ar putea fi colectat gunoiul înainte ca aceștia să fie dezînregistrați.
Ceea ce căutați, de fapt, este o metodă de înregistrare a ascultătorilor fără a le marca ca obiecte rădăcină vii. (Adresați-vă producătorului local de surse de evenimente pentru o astfel de caracteristică).

1): Ei bine, există un început cu specificarea lui WeakMap și WeakSet obiecte. Cu toate acestea, referințele slabe adevărate sunt încă în curs de realizare [1][2].

Comentarii

  • Dam, așteptând cu nerăbdare acești destructori Javascript reali. –  > Por Pacerier.
  • Acest lucru ar fi extrem de util. Ceva de genul celor din React cum va fi objectWillUnmount –  > Por Jason Sebring.
  • @nurettin Asta nu are nimic de-a face cu destructori?! (Btw, folosești ES8 async/await, nu ES9 Promise.prototype.finally.) –  > Por Bergi.
jfriend00

Tocmai am dat peste această întrebare în cadrul unei căutări despre destructori și m-am gândit că în comentariile dvs. a existat o parte a întrebării dvs. fără răspuns, așa că m-am gândit să mă adresez la asta.

vă mulțumesc băieți. Dar care ar fi o convenție bună dacă ECMAScript nu are destructori? Ar trebui să creez o metodă numită destructor și să o chem manual când am terminat cu obiectul? Orice altă idee?

Dacă doriți să îi spuneți obiectului dumneavoastră că ați terminat cu el și că ar trebui să eliberați în mod specific orice ascultători de evenimente pe care îi are, atunci puteți crea o metodă obișnuită pentru a face acest lucru. Puteți numi metoda ceva de genul release() sau deregister() sau unhook() sau ceva de acest gen. Ideea este că îi spuneți obiectului să se deconecteze de la orice altceva la care este conectat (deregistarea ascultătorilor de evenimente, ștergerea referințelor la obiecte externe etc.). Va trebui să o apelați manual la momentul potrivit.

Dacă, în același timp, vă asigurați, de asemenea, că nu există alte referințe la acel obiect, atunci obiectul dvs. va deveni eligibil pentru colectarea gunoiului în acel moment.

ES6 are weakMap și weakSet, care sunt modalități de a ține evidența unui set de obiecte care sunt încă în viață fără a afecta momentul în care acestea pot fi colectate la gunoi, dar nu oferă niciun fel de notificare atunci când acestea sunt colectate la gunoi. Acestea dispar pur și simplu din weakMap sau weakSet la un moment dat (când sunt GCed).


Pentru informarea dvs., problema cu acest tip de destructor pe care îl solicitați (și, probabil, motivul pentru care nu este prea solicitat) este că, din cauza colectării gunoiului, un element nu este eligibil pentru colectarea gunoiului atunci când are un manager de evenimente deschis în raport cu un obiect viu, astfel încât, chiar dacă ar exista un astfel de destructor, acesta nu ar fi apelat niciodată în circumstanțele dvs. până când nu ați eliminat de fapt ascultătorii de evenimente. Și, odată ce ați eliminat ascultătorii de evenimente, nu mai este nevoie de destructor în acest scop.

Presupun că există o posibilă weakListener() care să nu împiedice colectarea gunoiului, dar nici un astfel de lucru nu există.


Pentru informarea dvs., iată o altă întrebare relevantă De ce este omniprezentă paradigma destructorului de obiecte în limbajele cu colectare de gunoi?. Această discuție se referă la modelele de proiectare finalizer, destructor și disposer. Mi s-a părut util să văd distincția dintre cele trei.


Editare în 2020 – propunere de finalizator de obiecte

Există o propunere EMCAScript etapa 3 de adăugare a unei funcții de finalizare definite de utilizator după ce un obiect este colectat la gunoi.

Un exemplu canonic de obiect care ar beneficia de o astfel de funcție este un obiect care conține un handle la un fișier deschis. În cazul în care obiectul este colectat la gunoi (deoarece niciun alt cod nu mai are încă o referință la acesta), atunci această schemă de finalizare permite cel puțin să se afișeze un mesaj în consolă care să indice că o resursă externă tocmai a fost pierdută și că ar trebui corectat codul din altă parte pentru a preveni această scurgere.

Dacă citiți cu atenție propunerea, veți vedea că nu seamănă deloc cu un destructor complet într-un limbaj precum C++. Acest finalizator este apelat după ce obiectul a fost deja distrus și trebuie să predeterminăm ce parte a datelor de instanță trebuie să fie transmisă finalizatorului pentru ca acesta să își facă treaba. În plus, această caracteristică nu este menită să fie folosită pentru o funcționare normală, ci mai degrabă ca un ajutor pentru depanare și ca o protecție împotriva anumitor tipuri de erori. Puteți citi explicația completă a acestor limitări în propunere.

jgmjjgm

Trebuie să „distrugeți” manual obiectele în JS. Crearea unei funcții de distrugere este ceva obișnuit în JS. În alte limbaje, aceasta s-ar putea numi free, release, dispose, close etc. Din experiența mea, totuși, tinde să fie destroy, care va decupla referințele interne, evenimentele și, eventual, va propaga apelurile de distrugere și la obiectele copil.

WeakMaps sunt în mare parte inutile, deoarece nu pot fi iterați, iar acest lucru probabil nu va fi disponibil până la ECMA 7, dacă va fi cazul. Tot ceea ce vă permite WeakMaps este să aveți proprietăți invizibile detașate de obiectul în sine, cu excepția căutării prin referința obiectului și GC, astfel încât acestea să nu-l deranjeze. Acest lucru poate fi util pentru caching, extindere și gestionarea pluralității, dar nu ajută cu adevărat la gestionarea memoriei pentru observabile și observatori. WeakSet este un subset al WeakMap (ca un WeakMap cu o valoare implicită de boolean true).

Există diverse argumente cu privire la utilizarea diferitelor implementări ale referințelor slabe pentru acest lucru sau pentru destructori. Ambele au probleme potențiale, iar destructori sunt mai limitați.

De fapt, distrugătorii sunt potențial inutili și pentru observatori/ascultători, deoarece, de obicei, ascultătorul va deține referințe la observator, fie direct, fie indirect. Un distrugător funcționează cu adevărat doar în mod proxy, fără referințe slabe. În cazul în care observatorul dumneavoastră este doar un proxy care preia ascultătorii altcuiva și îi plasează pe un observabil, atunci poate face ceva acolo, dar acest tip de lucru este rareori util. Destructori sunt mai mult pentru lucruri legate de IO sau pentru a face lucruri în afara domeniului de cuprindere (IE, conectarea a două instanțe pe care le-a creat).

Cazul specific pentru care am început să cercetez acest lucru este pentru că am o instanță de clasă A care ia clasa B în constructor, apoi creează o instanță de clasă C care ascultă de B. Întotdeauna păstrez instanța B undeva la înălțime. Pe A o arunc uneori, creez altele noi, creez mai multe, etc. În această situație, un Destructor ar funcționa de fapt pentru mine, dar cu un efect secundar neplăcut că, în părintele dacă aș trece instanța C în jur, dar aș elimina toate referințele A, atunci legătura dintre C și B ar fi ruptă (C are pământul îndepărtat de sub ea).

În JS, faptul că nu există o soluție automată este dureros, dar nu cred că este ușor de rezolvat. Luați în considerare aceste clase (pseudo):

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.
    });
}

Ca o notă secundară, este greu să faci lucrurile să funcționeze fără funcții anonime/unice, care vor fi abordate mai târziu.

Într-un caz normal, instanțierea ar fi ca și cum (pseudo):

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

Pentru a le GC în mod normal, ar trebui să le setați la null, dar nu va funcționa deoarece au creat un arbore cu stdin la rădăcină. Aceasta este practic ceea ce fac sistemele de evenimente. Se dă un părinte unui copil, copilul se adaugă la părinte și apoi poate sau nu să mențină o referință la părinte. Un arbore este un exemplu simplu, dar în realitate vă puteți confrunta și cu grafuri complexe, deși rar.

În acest caz, Filter adaugă o referință la el însuși în stdin sub forma unei funcții anonime care face indirect referire la Filter prin domeniul de aplicare. Referințele de domeniu de aplicare sunt ceva de care trebuie să fim conștienți și care poate fi destul de complex. Un GC puternic poate face unele lucruri interesante pentru a ciopli elementele din variabilele de domeniu, dar acesta este un alt subiect. Ceea ce este esențial de înțeles este faptul că atunci când creați o funcție anonimă și o adăugați la ceva ca ascultător la ab observable, observabilul va menține o referință la funcție și tot ceea ce face referințe la funcție în sferele de deasupra ei (în care a fost definită) va fi, de asemenea, menținut. Vizualizările procedează la fel, dar după executarea constructorilor lor, copiii nu păstrează o referință la părinții lor.

Dacă setez oricare sau toate variabilele declarate mai sus la null, nu va face nicio diferență (la fel ca atunci când a terminat domeniul „main”). Ele vor fi în continuare active și vor conduce datele de la stdin la stdout și stderr.

Dacă le-aș seta pe toate la null, ar fi imposibil să fie eliminate sau GCed fără a șterge evenimentele de pe stdin sau fără a seta stdin la null (presupunând că poate fi eliberat astfel). Practic, în acest mod, aveți o scurgere de memorie cu obiecte de fapt orfane, dacă restul codului are nevoie de stdin și are alte evenimente importante pe el, ceea ce vă interzice să faceți ceea ce am menționat mai sus.

Pentru a scăpa de df, v1 și v2 trebuie să apelez la o metodă de distrugere pentru fiecare dintre ele. Din punct de vedere al implementării, acest lucru înseamnă că atât metodele Filter și View trebuie să păstreze referința la funcția de ascultare anonimă pe care o creează, precum și la observabil și să o treacă la removeListener.

În altă ordine de idei, puteți avea un observabil care returnează un index pentru a ține evidența ascultătorilor, astfel încât să puteți adăuga funcții prototipate, ceea ce, cel puțin din punctul meu de vedere, ar trebui să fie mult mai eficient din punct de vedere al performanței și al memoriei. Totuși, trebuie să țineți cont de identificatorul returnat și să treceți obiectul pentru a vă asigura că ascultătorul este legat de acesta atunci când este apelat.

O funcție de distrugere adaugă câteva dureri. În primul rând, ar trebui să o chem și să eliberez referința:

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

Aceasta este o neplăcere minoră, deoarece este un pic mai mult cod, dar nu aceasta este adevărata problemă. Când dau aceste referințe mai multor obiecte. În acest caz, când anume se apelează destroy? Nu puteți pur și simplu să le distribuiți altor obiecte. Veți ajunge la lanțuri de distrugeri și la implementarea manuală a urmăririi, fie prin fluxul programului, fie prin alte mijloace. Nu puteți trage și uita.

Un exemplu de acest tip de problemă este dacă decid că View va suna, de asemenea, destroy pe df atunci când acesta este distrus. Dacă v2 este încă pe aici, distrugerea lui df îl va distruge, astfel încât distrugerea nu poate fi pur și simplu transmisă către df. În schimb, atunci când v1 ia df pentru a-l utiliza, ar trebui să îi spună lui df că este utilizat, ceea ce ar ridica un contor sau ceva similar la df. Funcția de distrugere a lui df ar scădea acest contor și ar distruge efectiv doar dacă acesta este 0. Acest tip de lucru adaugă multă complexitate și multe lucruri care pot merge prost, dintre care cel mai evident este distrugerea unui obiect în timp ce încă mai există o referință pe undeva care va fi utilizată și referințe circulare (în acest moment nu mai este vorba de gestionarea unui contor, ci de o hartă a obiectelor de referință). Când vă gândiți să vă implementați propriile contoare de referințe, MM și așa mai departe în JS, atunci probabil că este deficitar.

Dacă WeakSets ar fi iterabile, ar putea fi folosit:

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);
};

În acest caz, clasa proprietară trebuie să păstreze, de asemenea, o referință simbolică la f, altfel va dispărea.

Dacă s-ar folosi Observable în loc de EventListener, atunci gestionarea memoriei ar fi automată în ceea ce privește ascultătorii de evenimente.

În loc să se apeleze la distrugerea fiecărui obiect, acest lucru ar fi suficient pentru a le elimina complet:

df = v1 = v2 = null;

Dacă nu ați setat df la null, acesta ar exista în continuare, dar v1 și v2 ar fi automat dezactivate.

Există totuși două probleme cu această abordare.

Prima problemă este că adaugă o nouă complexitate. Uneori, oamenii nu doresc de fapt acest comportament. Aș putea să creez un lanț foarte mare de obiecte legate între ele prin evenimente mai degrabă decât prin conținut (referințe în domeniul de aplicare al constructorilor sau în proprietățile obiectelor). În cele din urmă, un arbore și ar trebui să trec doar în jurul rădăcinii și să mă îngrijorez de acest lucru. Eliberarea rădăcinii ar elibera în mod convenabil întregul obiect. Ambele comportamente, în funcție de stilul de codare etc., sunt utile, iar atunci când se creează obiecte reutilizabile va fi greu fie să știi ce vor oamenii, ce au făcut ei, ce ai făcut tu și o pacoste să lucrezi în jurul a ceea ce s-a făcut. Dacă folosesc Observable în loc de EventListener, atunci fie df va trebui să facă referință la v1 și v2, fie va trebui să le trec pe toate dacă vreau să transfer proprietatea referinței către altceva din afara domeniului de aplicare. O referință slabă de genul unei referințe ar atenua puțin problema prin transferul controlului de la Observable la un observator, dar nu ar rezolva-o în întregime (și are nevoie de verificare la fiecare emitere sau eveniment asupra sa). Această problemă poate fi rezolvată, presupun, dacă acest comportament se aplică numai la grafurile izolate, ceea ce ar complica grav GC și nu s-ar aplica în cazurile în care există referințe în afara grafului care, în practică, sunt noops (consumă doar cicluri CPU, nu se fac modificări).

A doua problemă este că fie este imprevizibil în anumite cazuri, fie forțează motorul JS să parcurgă graficul GC pentru acele obiecte la cerere, ceea ce poate avea un impact îngrozitor asupra performanței (deși, dacă este inteligent, poate evita să facă acest lucru pentru fiecare membru, făcându-l pentru fiecare buclă WeakMap). Este posibil ca GC să nu se execute niciodată în cazul în care utilizarea memoriei nu atinge un anumit prag, iar obiectul și evenimentele sale nu vor fi eliminate. Dacă setez v1 la null, este posibil ca acesta să transmită în continuare la stdout pentru totdeauna. Chiar dacă se face GCed, acest lucru va fi arbitrar, acesta poate continua să transmită la stdout pentru orice perioadă de timp (1 linie, 10 linii, 2,5 linii etc.).

Motivul pentru care WeakMap poate scăpa fără să se preocupe de GC atunci când nu poate fi reiterat este că, pentru a accesa un obiect, trebuie să aveți oricum o referință la acesta, deci fie nu a fost GCed, fie nu a fost adăugat la hartă.

Nu sunt sigur de ceea ce cred despre acest tip de lucru. Într-un fel, spargi gestionarea memoriei pentru a o rezolva cu abordarea WeakMap iterabilă. Problema a doua poate exista și pentru destructori, de asemenea.

Toate acestea invocă mai multe niveluri ale iadului, așa că aș sugera să încercați să le ocoliți cu o bună proiectare a programului, cu bune practici, evitând anumite lucruri, etc. Totuși, poate fi frustrant în JS, din cauza cât de flexibil este în anumite aspecte și pentru că este mai natural asincron și bazat pe evenimente, cu o puternică inversare a controlului.

Există o altă soluție care este destul de elegantă, dar care, din nou, are încă unele probleme potențial grave. Dacă aveți o clasă care extinde o clasă observabilă, puteți suprascrie funcțiile de eveniment. Adăugați evenimentele dvs. la alte observabile numai atunci când evenimentele sunt adăugate la dvs. Când toate evenimentele sunt eliminate de la dumneavoastră, atunci eliminați evenimentele de la copii. Puteți, de asemenea, să creați o clasă care să extindă clasa observabilă pentru a face acest lucru pentru dumneavoastră. O astfel de clasă ar putea oferi cârlige pentru gol și nevid, astfel încât, de fapt, să vă observați pe dumneavoastră însuși. Această abordare nu este rea, dar are și impedimente. Există o creștere a complexității, precum și o scădere a performanței. Va trebui să păstrați o referință la obiectul pe care îl observați. În mod critic, aceasta nu va funcționa nici pentru frunze, dar cel puțin intermediarii se vor autodistruge dacă distrugeți frunza. Este ca și cum ați înlănțui distrugerea în lanț, dar ascunsă în spatele apelurilor pe care trebuie deja să le înlănțuiți. Totuși, o mare problemă de performanță este că este posibil să trebuiască să reinitializați datele interne din Observable de fiecare dată când clasa dvs. devine activă. Dacă acest proces durează foarte mult timp, atunci s-ar putea să aveți probleme.

Dacă ați putea să iterați WeakMap, atunci ați putea, poate, să combinați lucrurile (să treceți la Weak când nu există evenimente, Strong când există evenimente), dar tot ceea ce faceți este să puneți problema de performanță pe seama altcuiva.

Există, de asemenea, neplăceri imediate cu WeakMap iterabil atunci când vine vorba de comportament. Am menționat pe scurt mai devreme despre funcțiile care au referințe de domeniu de aplicare și despre carving. Dacă instanțiez un copil care în constructorul care agață ascultătorul „console.log(param)” la părinte și nu reușește să persiste la părinte, atunci când elimin toate referințele la copil, acesta ar putea fi eliberat în întregime, deoarece funcția anonimă adăugată la părinte nu face referire la nimic din interiorul copilului. Rămâne întrebarea ce trebuie să facem cu parent.weakmap.add(child, (param) => console.log(param)). Din câte știu eu, cheia este slabă, dar nu și valoarea, astfel încât weakmap.add(object, object) este persistentă. Totuși, este ceva ce trebuie să reevaluez. Mie mi se pare o scurgere de memorie dacă elimin toate celelalte referințe de obiect, dar bănuiesc că în realitate gestionează acest lucru, practic, văzând-o ca pe o referință circulară. Fie funcția anonimă menține o referință implicită la obiectele care rezultă din domeniile părintești pentru coerență, irosind o mulțime de memorie, fie comportamentul variază în funcție de circumstanțe, ceea ce este greu de prevăzut sau de gestionat. Cred că prima variantă este de fapt imposibilă. În cel de-al doilea caz, dacă aș avea o metodă într-o clasă care pur și simplu ia un obiect și adaugă console.log, acesta ar fi eliberat atunci când șterg referințele la clasă, chiar dacă aș returna funcția și aș menține o referință. Pentru a fi corect, acest scenariu particular este rareori necesar în mod legitim, dar în cele din urmă cineva va găsi un unghi de abordare și va cere un HalfWeakMap care este iterabil (liber pe cheia și valoarea refs eliberate), dar care este de asemenea imprevizibil (obj = null terminând în mod magic IO, f = null terminând în mod magic IO, ambele realizabile la distanțe incredibile).

Comentarii

  • Există vreo discuție între planificatorii și proiectanții ES pentru a adăuga în cele din urmă cârlige în sistemul GC? –  > Por CMCDragonkai.
Craig Hicks

Dacă nu există un astfel de mecanism, care este un model/convenție pentru astfel de probleme?

Termenul „cleanup” ar putea fi mai potrivit, dar se va folosi „destructor” pentru a se potrivi cu OP

Să presupunem că scrieți niște javascript în întregime cu ‘function’s și ‘var’s. Atunci puteți folosi modelul de a scrie toate functions în cadrul unui cod de tip try/catch/finally de o rețea. În cadrul finally se efectuează codul de distrugere.

În locul stilului C++ de a scrie clase de obiecte cu durate de viață nespecificate și apoi de a specifica durata de viață prin domenii arbitrare și apelul implicit la ~() la sfârșitul domeniului de aplicare (~() este destructorul în C++), în acest model javascript, obiectul este funcția, domeniul de aplicare este exact domeniul de aplicare al funcției, iar destructorul este finally bloc.

Dacă vă gândiți acum că acest model este în mod inerent eronat, deoarece try/catch/finally nu include execuția asincronă, care este esențială pentru javascript, atunci aveți dreptate. Din fericire, din 2018, din 2018, obiectul ajutător de programare asincronă Promise a avut o funcție prototip finally adăugată la funcția deja existentă resolve și catch funcțiile de prototip. Acest lucru înseamnă că, astfel, domeniile asincrone care necesită destructori pot fi scrise cu o funcție Promise folosind un obiect finally ca destructor. În plus, puteți utiliza try/catch/finally în cadrul unui async function de apelare Promises cu sau fără await, dar trebuie să fie conștienți de faptul că Promises apelat fără await va fi executat în mod asincron în afara domeniului de aplicare și, prin urmare, trebuie să trateze codul desctructor într-un final. then.

În următorul cod PromiseA și PromiseB sunt niște promisiuni la nivel de API moștenite care nu au finally argumente de funcție specificate. PromiseC DOAR au un argument finally definit.

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 */ }
}

Nu pledez pentru ca fiecare obiect din javascript să fie scris ca o funcție. În schimb, luați în considerare cazul în care ați identificat un domeniu de aplicare care „dorește” cu adevărat ca un destructor să fie apelat la sfârșitul vieții sale. Formulați acest domeniu de aplicare ca un obiect funcție, utilizând modelul finally block (sau finally funcție în cazul unui domeniu de aplicare asincron) ca destructor. Este foarte probabil ca formularea acestui obiect funcțional să fi eliminat necesitatea unei clase nefuncționale care ar fi fost scrisă altfel – nu a fost nevoie de cod suplimentar, alinierea domeniului de aplicare și a clasei ar putea fi chiar mai curată.

Notă: Așa cum au scris și alții, nu trebuie să confundăm destructori și garbage collection. Așa cum se întâmplă, destructori C++ sunt adesea sau în principal preocupați de colectarea manuală a gunoiului, dar nu și exclusiv așa. Javascript nu are nevoie de colectarea manuală a gunoiului, dar sfârșitul asincron al domeniului de aplicare este adesea un loc pentru (de)înregistrarea ascultătorilor de evenimente, etc..

Cliff Hall

„Un destructor nici măcar nu te-ar ajuta aici. Ascultătorii de evenimente înșiși sunt cei care încă fac referire la obiectul dvs., astfel încât acesta nu ar putea fi colectat de gunoi înainte ca aceștia să fie dezînregistrați.”

Nu este așa. Scopul unui destructor este de a permite obiectului care a înregistrat ascultătorii să îi dezînregistreze. Odată ce un obiect nu mai are alte referințe la el, acesta va fi colectat la gunoi.

De exemplu, în AngularJS, atunci când un controler este distrus, acesta poate asculta un eveniment de distrugere și răspunde la acesta. Acest lucru nu este același lucru cu apelarea automată a unui destructor, dar este apropiat și ne oferă posibilitatea de a elimina ascultătorii care au fost setați atunci când controlerul a fost inițializat.

// Set event listeners, hanging onto the returned listener removal functions
function initialize() {
    $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 ) );
}

// Remove event listeners when the controller is destroyed
function onDestroy(){
    $scope.listenerCleanup.forEach( remove => remove() );
}