ECMAScript 6 class destructor

I know ECMAScript 6 has constructors, but is there such a thing as destructors for ECMAScript 6?

For example, if I register some of my object methods as event listeners in the constructor, I want to delete them when my object is deleted.

One solution is to have an agreement to create a desctructor method for each class that needs this behavior, and call it manually. This will remove the links to the event handlers, so my object will be really ready for garbage collection. Otherwise, it will remain in memory due to these methods.

But I was hoping that if ECMAScript 6 had something native that would be called right before the object was garbage collected.

If such a mechanism is missing, what is the pattern / convention for such problems?

+47
javascript ecmascript-6
Mar 29 '15 at 18:22
source share
5 answers

Is there such a thing as destructors for ECMAScript 6?

No. EcmaScript 6 does not define garbage collection semantics at all [1] so there is nothing like "destruction".

If I register some of my object methods as event listeners in the constructor, I want to delete them when my object is deleted.

The destructor will not even help you. These are event listeners themselves that still reference your object, so it will not be able to collect garbage before they are unregistered.
What you are actually looking for is a method of registering listeners without marking them as living root objects. (Ask your local event source maker for such a feature.)

1): Well, there is a beginning with the specification of WeakMap and WeakSet . However, true weak links are still under development [1] [2] .sub>

+17
Mar 29 '15 at 22:55
source share

I just stumbled upon this question in the search for destructors, and I thought that in your comments the unanswered part of your question was left, so I thought that I would consider this.

Thank you guys. But what would be a good deal if ECMAScript has no destructors? Should I create a method called a destructor and call it manually when I am done with the object? Any other idea?

If you want to tell your object that you have done it now, and it must specifically release any event listeners that it has, then you can simply create a regular method for this. You can call a method like release() or deregister() or unhook() or something like that. The idea is that you tell the object to disconnect itself from everything connected with it (unregister events, clear links to external objects, etc.). You will need to call it manually at the appropriate time.

If at the same time you are also convinced that there are no other references to this object, then your object will have the right to garbage collection at this point.

ES6 has weakMap and weakSet, which are ways to track a collection of objects that are still alive, without affecting when they can be garbage collected, but do not provide any notification when they are garbage collected. They just disappear from weakMap or weakSet at some point (when they are GCed).




FYI, the problem with this type of destructor that you are asking for (and probably why there isnโ€™t so much of it) is that due to garbage collection, the element does not have the right to garbage collection when it has an open event handler against a living object therefore, even if such a destructor existed, it would never be called in your circumstances until you actually delete event listeners. And, once you remove the event listeners, there is no need for a destructor for this purpose.

I assume that there is a possible weakListener() that will not prevent garbage collection, but such a thing also does not exist.




FYI, here's another topical question Why does the object destructor paradigm in garbage collections constantly disappear? . This discussion discusses finalizer, destructor, and delete development patterns. It was good for me to see the difference between the three.

+21
Jan 31 '16 at 23:38
source share

You need to manually โ€œdestroyโ€ objects in JS. Creating a kill function is common in JS. In other languages, this can be called free, release, dispose, close, etc. In my experience, although it tends to be destroyed, which detaches internal links, events, and possibly distributes, it also destroys calls to child objects.

WeakMaps are pretty much useless as they cannot be repeated, and it probably won't be available until ECMA 7, if at all. All WeakMaps allow you to have invisible properties that are separate from the object itself, except to search by the object's link and GC so that they do not interfere with it. This may be useful for caching, expanding, and using plurality, but it does not help in memory management for observers and observers. WeakSet is a subset of WeakMap (for example, WeakMap with the default value for the boolean value true).

There are various arguments as to whether different implementations of weak references should be used for this or destructors. Both have potential problems, and destructors are more limited.

Destructors are virtually useless for observers / listeners, because usually the listener will directly or indirectly refer to the observer. The destructor really only works in the proxy module without weak links. If your Observer is really just a proxy server that takes something else from the listeners and puts them in observables, then it can do something, but such things are rarely useful. Destructors are more related to events related to IO, or do something outside the scope of containment (IE, linking the two instances that it created).

The specific case that I started to study is because I have an instance of class A that takes class B in the constructor and then creates an instance of class C that listens to B. I always keep instance B somewhere above. AI sometimes throws, creates new, creates many, etc. In this situation, the destructor really works for me, but with the unpleasant side effect that the parent has, if I passed the instance of C around, but deleted all the links A, then C and Binding B will be broken (C removes the ground from under it).

In JS that does not have an automatic solution, it hurts, but I don't think it is easily solvable. Consider these classes (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. }); } 

On the other hand, it is difficult to do the job without anonymous / unique functions, which will be discussed later.

In the normal case, the instance will be the same (pseudo):

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

In GC, these usually you should set them to null, but this will not work because they created a tree with stdin in the root. This is basically what event systems do. You give the parent to the child, the child adds himself to the parent, and then may or may not contain a link to the parent. A tree is a simple example, but in fact you can also end up with complex graphs, albeit rarely.

In this case, the filter adds a reference to itself on stdin in the form of an anonymous function, which indirectly refers to Filter by scope. Linking is something you need to know about, and it can be quite complicated. A powerful GC can do some interesting things to cut objects in variable areas, but that's another topic. It is important to understand that when you create an anonymous function and add it to something as a listener for ab of the observable, the observable will maintain a reference to the function and anything that the function refers to in the areas above it (that it was defined in) will also be saved. Representations do the same, but after executing their constructors, children do not maintain a reference to their parents.

If I set all or all of the vars declared above equal to zero, this will have nothing to do with anything (similarly, when he finishes this "main" area). They will still be active and pass data from stdin to stdout and stderr.

If I set them all to null, it would be impossible to remove them or compile them without clearing events on stdin or setting stdin to null (provided that it can be freed up like that). You basically have a memory leak in such a way that as a result of orphaned objects, if the rest of the code needs stdin and has other important events preventing you from doing the above.

To get rid of df, v1 and v2, I need to call the destroy method for each of them. From an implementation point of view, this means that both the Filter and View methods must contain a reference to the anonymous listener function that they create, as well as observables, and pass this to remove the Listener.

On the side of the note, as an alternative, you can have an observable that returns an index to track listeners so you can add prototyped functions that, at least for my understanding, should be much better in performance and memory. You still need to keep track of the returned identifier and pass in your object to ensure that the listener is associated with it when called.

The kill function adds a few pains. First, I would have to call it and release the link:

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

This is a minor annoyance as it is a bit more code, but this is not a real problem. When I pass these links to many objects. In this case, when exactly do you call destruction? You cannot just pass them to other objects. You will receive destruction chains and manual tracking implementation either through the program flow or some other means. You cannot shoot and forget.

An example of this type of problem is that I decided that View would also call destroy on df when it was destroyed. If v2 still destroys df, it will break it, so you can't just destroy it in df. Instead, when v1 accepts df to use it, he will need to say that it is used to raise some counter or similarly df. The df destroy function will decrease than the counter, and only actually destroy if it is 0. This thing adds a lot of complexity and adds a lot that can go wrong, the most obvious of which destroys something while there is somewhere cyclic links will be used (at the moment this is not a case of counter control, but a map of link objects). When you think about implementing your own link counters, MM, etc. In JS, this is probably not enough.

If WeakSets were iterable, this could be used:

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

In this case, the native class must also contain a reference to the token, otherwise it will be a navel.

If Observable was used instead of EventListener, then memory management would be automatic with respect to event listeners.

Instead of calling destroy on each object, this will be enough to completely remove them:

 df = v1 = v2 = null; 

If you did not set df to null, it will still exist, but v1 and v2 will be automatically detached.

However, there are two problems with this approach.

The problem is that it adds new complexity. Sometimes people really do not want this behavior. I could create a very large chain of objects related to each other by events, rather than deterrence (links in constructor objects or object properties). Ultimately, the tree and I would only have to go around the root and worry about it. Freeing the root comfortably frees everything. Both behaviors depending on coding style, etc. Itโ€™s useful and when creating reusable objects it will be difficult to know what people want, what they did, what you did and the pain to get around what was done. If I use Observable instead of EventListener, then either df will need to reference v1 and v2, or I will have to transfer them all if I want to transfer ownership of the link to something else from scope. A weak link like this thing would slightly alleviate the problem by transferring control from the observed to the observer, but would not solve it completely (and needs to be checked on every emission or event on its own). This problem can be fixed. I believe that if the behavior is applicable only to isolated graphs, which will seriously complicate the GC and will not apply to cases where there are links outside the graph that are noops in practice (only consume processor cycles, no changes have been made).

The problem is that it either is unpredictable in some cases, or causes the JS engine to cross the GC graph for those objects on demand that can have a terrible effect on performance (although if it is smart, it can avoid this element from doing this in one WeakMap cycle). GC will never work unless memory usage reaches a certain threshold and the object with its events is not deleted. If I set v1 to null, it can still pass to stdout forever. Even if he gets GCed, it will be arbitrary, he can continue to relay to stdout for any amount of time (1 line, 10 lines, 2.5 lines, etc.).

The reason WeakMap leaves without worrying about the GC, when non-iterable is that to access the object you have a link to it anyway, either it was not GCed or was not added to the map.

I'm not sure what I think about it. You kind of tear up the memory management to fix it with the WeakMap iterative approach. A second problem may exist for destructors.

All this causes several levels of hell, so I suggest trying to get around it with good program design, good practice, avoiding certain things, etc. However, this can upset JS because of how flexible it is in certain and also because it is more naturally asynchronous and based on events with heavy control inversion.

There is another pretty elegant solution, but it still has potentially serious hangs. If you have a class that extends an observable class, you can override the functions of the event. Add your events to other observables only when events are added to yourself. When all events are deleted from you, delete events from children. You can also make a class to expand your observable class, to do it for you. Such a class can provide hooks for empty and non-empty ones, so as you will be watching yourself. This approach is not bad, but also has freezes. There is an increase in complexity as well as a decrease in performance. You will need to save the link to the object you are observing. Critically, this will also not work for leaves, but at least intermediate products will self-destruct if you destroy the leaf. It looks like a chain of destruction, but hidden behind calls that you already need to cling to. The big performance issue is that you may have to reinitialize the internal data from the Observable every time your class becomes active. If this process takes a very long time, you may have problems.

If you could iterate over WeakMap, then you could combine things (switch to โ€œWeakโ€ if there are no events, Strong when events), but all that really does is a performance issue on someone else.

There is also immediate trouble with the iterable WeakMap when it comes to behavior. I briefly mentioned functions that have references to scope and thread. If I create an instance of a child in a constructor that intercepts the console.log (param) listener of the parent and cannot save the parent, then when I delete all references to the child, it can be completely freed because an anonymous function is added The parent does not bind anything to the child. This leaves the question of what to do with parent.weakmap.add (child, (param) => console.log (param)). As far as I know, the key is weak, but not the value, so weakmap.add (object, object) is constant. This is what I need to overestimate. To me, it looks like a memory leak if I dispose of all the other object references, but I suspect that it actually controls what it basically sees as a circular reference. Any anonymous function maintains an implicit reference to objects obtained from parent areas for consistency, which consumes a lot of memory, or you have behavior that depends on circumstances that are difficult to predict or manage. I think the first is actually impossible. , , console.log, , , . , , - HalfWeakMap, ( ), (obj = null magical end IO, f = null IO, ).

+9
21 . '16 22:37
source share

" . , , ".

Not this way. - , , . , .

, AngularJS, , . , , , .

 // 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() ); } 
+1
29 . '17 15:56
source share

, / ?

"" , "", OP

, - javascript 'function and' var's. 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 , , Promise await, , then .

PromiseA PromiseB - API, finally . PromiseC .

 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 ). , , - , .

Note: as others wrote, we should not confuse destructors and garbage collectors. As it happens, C ++ destructors often or mainly do manual garbage collection, but not exclusively. Javascript does not require manual garbage collection, but the end of life of an asynchronous area is often the place (for) registering event listeners, etc.

0
Jul 10 '19 at 7:55
source share



All Articles