Javascript ECMAScript 6 类析构函数
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/29333017/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ECMAScript 6 class destructor
提问by AlexStack
I know ECMAScript 6 has constructors but is there such a thing as destructors for ECMAScript 6?
我知道 ECMAScript 6 有构造函数,但是有没有 ECMAScript 6 的析构函数这样的东西?
For example if I register some of my object's methods as event listeners in the constructor, I want to remove them when my object is deleted.
例如,如果我在构造函数中将我的一些对象方法注册为事件侦听器,我想在我的对象被删除时删除它们。
One solution is to have a convention of creating a desctructormethod for every class that needs this kind of behaviour and manually call it. This will remove the references to the event handlers, hence my object will truly be ready for garbage collection. Otherwise it'll stay in memory because of those methods.
一种解决方案是约定desctructor为每个需要这种行为的类创建一个方法并手动调用它。这将删除对事件处理程序的引用,因此我的对象将真正准备好进行垃圾收集。否则它会因为这些方法而留在内存中。
But I was hoping if ECMAScript 6 has something native that will be called right before the object is garbage collected.
但我希望 ECMAScript 6 是否有一些原生的东西可以在对象被垃圾收集之前被调用。
If there is no such mechanism, what is a pattern/convention for such problems?
如果没有这样的机制,那么针对此类问题的模式/约定是什么?
采纳答案by Bergi
Is there such a thing as destructors for ECMAScript 6?
ECMAScript 6 是否有析构函数这样的东西?
No. EcmaScript 6 does not specify any garbage collection semantics at all[1], so there is nothing like a "destruction" either.
不。EcmaScript 6 根本没有指定任何垃圾收集语义[1],所以也没有什么像“破坏”。
If I register some of my object's methods as event listeners in the constructor, I want to remove them when my object is deleted
如果我在构造函数中将我的一些对象的方法注册为事件侦听器,我想在我的对象被删除时删除它们
A destructor wouldn't even help you here. It's the event listeners themselves that still reference your object, so it would not be able to get garbage-collected before they are unregistered.
What you are actually looking for is a method of registering listeners without marking them as live root objects. (Ask your local eventsource manufacturer for such a feature).
析构函数在这里甚至不会帮助你。事件侦听器本身仍然引用您的对象,因此在它们未注册之前将无法进行垃圾收集。
您实际上正在寻找的是一种注册侦听器而不将它们标记为活动根对象的方法。(向您当地的事件源制造商咨询此类功能)。
1): Well, there is a beginning with the specification of WeakMapand WeakSetobjects. However, true weak references are still in the pipeline [1][2].
回答by jfriend00
I just came across this question in a search about destructors and I thought there was an unanswered part of your question in your comments, so I thought I would address that.
我刚刚在搜索析构函数时遇到了这个问题,我认为你的评论中有一个未回答的问题,所以我想我会解决这个问题。
thank you guys. But what would be a good convention if ECMAScript doesn't have destructors? Should I create a method called destructor and call it manually when I'm done with the object? Any other idea?
谢谢你们。但是如果 ECMAScript 没有析构函数,什么是好的约定?我应该创建一个称为析构函数的方法并在我完成对象后手动调用它吗?还有其他想法吗?
If you want to tell your object that you are now done with it and it should specifically release any event listeners it has, then you can just create an ordinary method for doing that. You can call the method something like release()or deregister()or unhook()or anything of that ilk. The idea is that you're telling the object to disconnect itself from anything else it is hooked up to (deregister event listeners, clear external object references, etc...). You will have to call it manually at the appropriate time.
如果你想告诉你的对象你现在已经完成了它并且它应该专门释放它拥有的任何事件侦听器,那么你可以创建一个普通的方法来做到这一点。您可以调用类似release()或deregister()或unhook()或类似的方法。这个想法是你告诉对象将它自己与它所连接的任何其他东西断开连接(取消注册事件侦听器,清除外部对象引用等......)。您必须在适当的时间手动调用它。
If, at the same time you also make sure there are no other references to that object, then your object will become eligible for garbage collection at that point.
如果同时您还确保没有对该对象的其他引用,那么您的对象此时将有资格进行垃圾回收。
ES6 does have weakMap and weakSet which are ways of keeping track of a set of objects that are still alive without affecting when they can be garbage collected, but it does not provide any sort of notification when they are garbage collected. They just disappear from the weakMap or weakSet at some point (when they are GCed).
ES6 确实有 weakMap 和 weakSet ,它们是跟踪一组仍然活着的对象而不影响它们何时可以被垃圾收集的方法,但是当它们被垃圾收集时它不提供任何类型的通知。它们只是在某个时候(当它们被 GC 处理时)从 weakMap 或 weakSet 中消失。
FYI, the issue with this type of destructor you ask for (and probably why there isn't much of a call for it) is that because of garbage collection, an item is not eligible for garbage collection when it has an open event handler against a live object so even if there was such a destructor, it would never get called in your circumstance until you actually removed the event listeners. And, once you've removed the event listeners, there's no need for the destructor for this purpose.
仅供参考,您要求的这种类型的析构函数的问题(可能为什么没有太多调用它)是由于垃圾收集,当项目有一个打开的事件处理程序时,它不符合垃圾收集的条件一个活动对象,因此即使有这样的析构函数,在您实际删除事件侦听器之前,它也永远不会在您的情况下被调用。而且,一旦您删除了事件侦听器,就不需要为此目的使用析构函数。
I suppose there's a possible weakListener()that would not prevent garbage collection, but such a thing does not exist either.
我想有可能weakListener()不会阻止垃圾收集,但这样的事情也不存在。
FYI, here's another relevant question Why is the object destructor paradigm in garbage collected languages pervasively absent?. This discussion covers finalizer, destructor and disposer design patterns. I found it useful to see the distinction between the three.
仅供参考,这是另一个相关问题为什么垃圾收集语言中的对象析构函数范式普遍不存在?. 此讨论涵盖终结器、析构器和处置器设计模式。我发现查看三者之间的区别很有用。
Edit in 2020 - proposal for object finalizer
2020 年编辑 - 对象终结器提案
There is a Stage 3 EMCAScript proposalto add a user-defined finalizer function after an object is garbage collected.
有一个第 3 阶段的 EMCAScript 提议在对象被垃圾收集后添加用户定义的终结器函数。
A canonical example of something that would benefit from a feature like this is an object that contains a handle to an open file. If the object is garbage collected (because no other code still has a reference to it), then this finalizer scheme allows one to at least put a message to the console that an external resource has just been leaked and code elsewhere should be fixed to prevent this leak.
可以从此类功能中受益的典型示例是包含打开文件句柄的对象。如果对象被垃圾回收(因为没有其他代码仍然引用它),那么这个终结器方案至少允许向控制台发送一条消息,表明外部资源刚刚泄漏,并且应该修复其他地方的代码以防止这个泄漏。
If you read the proposal thoroughly, you will see that it's nothing like a full-blown destructor in a language like C++. This finalizer is called after the object has already been destroyed and you have to predetermine what part of the instance data needs to be passed to the finalizer for it to do its work. Further, this feature is not meant to be relied upon for normal operation, but rather as a debugging aid and as a backstop against certain types of bugs. You can read the full explanation for these limitations in the proposal.
如果您仔细阅读提案,您会发现它与 C++ 等语言中的成熟析构函数完全不同。这个终结器在对象已经被销毁之后被调用,你必须预先确定实例数据的哪一部分需要传递给终结器才能完成它的工作。此外,此功能并不意味着要依赖于正常操作,而是作为调试帮助和针对某些类型错误的支持。您可以在提案中阅读对这些限制的完整解释。
回答by jgmjgm
You have to manually "destruct" objects in JS. Creating a destroy function is common in JS. In other languages this might be called free, release, dispose, close, etc. In my experience though it tends to be destroy which will unhook internal references, events and possibly propagates destroy calls to child objects as well.
您必须在 JS 中手动“销毁”对象。创建销毁函数在 JS 中很常见。在其他语言中,这可能被称为 free、release、dispose、close 等。根据我的经验,虽然它往往是 destroy ,它会解除内部引用、事件并可能将销毁调用传播到子对象。
WeakMaps are largely useless as they cannot be iterated and this probably wont be available until ECMA 7 if at all. All WeakMaps let you do is have invisible properties detached from the object itself except for lookup by the object reference and GC so that they don't disturb it. This can be useful for caching, extending and dealing with plurality but it doesn't really help with memory management for observables and observers. WeakSet is a subset of WeakMap (like a WeakMap with a default value of boolean true).
WeakMaps 在很大程度上是无用的,因为它们无法迭代,而且这可能要到 ECMA 7 才能使用,如果有的话。除了通过对象引用和 GC 查找之外,所有 WeakMap 都让您可以将不可见的属性与对象本身分离,以便它们不会干扰它。这对于缓存、扩展和处理多个很有用,但它并没有真正帮助 observables 和观察者的内存管理。WeakSet 是 WeakMap 的一个子集(就像一个默认值为 boolean true 的 WeakMap)。
There are various arguments on whether to use various implementations of weak references for this or destructors. Both have potential problems and destructors are more limited.
关于是否对 this 或析构函数使用弱引用的各种实现存在各种争论。两者都有潜在的问题,析构函数更有限。
Destructors are actually potentially useless for observers/listeners as well because typically the listener will hold references to the observer either directly or indirectly. A destructor only really works in a proxy fashion without weak references. If your Observer is really just a proxy taking something else's Listeners and putting them on an observable then it can do something there but this sort of thing is rarely useful. Destructors are more for IO related things or doing things outside of the scope of containment (IE, linking up two instances that it created).
析构函数实际上对于观察者/侦听器也可能无用,因为通常侦听器将直接或间接持有对观察者的引用。析构函数只在没有弱引用的情况下真正以代理方式工作。如果您的 Observer 真的只是一个代理,它接收其他东西的 Listeners 并将它们放在 observable 上,那么它可以在那里做一些事情,但这种事情很少有用。析构函数更多地用于与 IO 相关的事情或在包含范围之外做事情(IE,链接它创建的两个实例)。
The specific case that I started looking into this for is because I have class A instance that takes class B in the constructor, then creates class C instance which listens to B. I always keep the B instance around somewhere high above. A I sometimes throw away, create new ones, create many, etc. In this situation a Destructor would actually work for me but with a nasty side effect that in the parent if I passed the C instance around but removed all A references then the C and B binding would be broken (C has the ground removed from beneath it).
我开始研究这个的具体情况是因为我有一个类 A 实例,它在构造函数中接受类 B,然后创建类 C 实例来侦听 B。我总是将 B 实例放在上方的某个地方。AI 有时会扔掉,创建新的,创建许多等等。在这种情况下,析构函数实际上对我有用,但是如果我传递 C 实例但删除所有 A 引用然后删除 C 和B 绑定将被破坏(C 已从其下方移除地面)。
In JS having no automatic solution is painful but I don't think it's easily solvable. Consider these classes (pseudo):
在 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.
});
}
On a side note, it's hard to make things work without anonymous/unique functions which will be covered later.
附带说明一下,如果没有匿名/独特的功能,就很难让事情工作起来,稍后将介绍。
In a normal case instantiation would be as so (pseudo):
在正常情况下,实例化将如此(伪):
var df = new Filter(stdin),
v1 = new View(df, stdout),
v2 = new View(df, stderr);
To GC these normally you would set them to null but it wont work because they've created a tree with stdin at the root. This is basically what event systems do. You give a parent to a child, the child adds itself to the parent and then may or may not maintain a reference to the parent. A tree is a simple example but in reality you may also find yourself with complex graphs albeit rarely.
要 GC 这些通常你会将它们设置为 null 但它不会工作,因为它们已经在根创建了一个带有 stdin 的树。这基本上就是事件系统所做的。你给一个孩子一个父母,孩子将自己添加到父母,然后可能会或可能不会维护对父母的引用。树是一个简单的例子,但实际上你也可能会发现自己有复杂的图形,尽管很少。
In this case, Filter adds a reference to itself to stdin in the form of an anonymous function which indirectly references Filter by scope. Scope references are something to be aware of and that can be quite complex. A powerful GC can do some interesting things to carve away at items in scope variables but that's another topic. What is critical to understand is that when you create an anonymous function and add it to something as a listener to ab observable, the observable will maintain a reference to the function and anything the function references in the scopes above it (that it was defined in) will also be maintained. The views do the same but after the execution of their constructors the children do not maintain a reference to their parents.
在这种情况下,Filter 以匿名函数的形式向 stdin 添加对自身的引用,该函数通过作用域间接引用 Filter。范围引用是需要注意的,而且可能非常复杂。强大的 GC 可以做一些有趣的事情来清除范围变量中的项目,但这是另一个话题。理解的关键是,当您创建一个匿名函数并将其添加到某物作为 ab observable 的侦听器时,该 observable 将维护对该函数的引用以及该函数在其上方作用域中引用的任何内容(它在) 也将得到维护。视图做同样的事情,但是在执行它们的构造函数之后,孩子们不会维护对他们父母的引用。
If I set any or all of the vars declared above to null it isn't going to make a difference to anything (similarly when it finished that "main" scope). They will still be active and pipe data from stdin to stdout and stderr.
如果我将上面声明的任何或所有变量设置为 null,它不会对任何事情产生影响(类似地,当它完成“主要”范围时)。它们仍将处于活动状态,并将数据从 stdin 传送到 stdout 和 stderr。
If I set them all to null it would be impossible to have them removed or GCed without clearing out the events on stdin or setting stdin to null (assuming it can be freed like this). You basically have a memory leak that way with in effect orphaned objects if the rest of the code needs stdin and has other important events on it prohibiting you from doing the aforementioned.
如果我将它们全部设置为 null,则不可能在不清除 stdin 上的事件或将 stdin 设置为 null(假设它可以像这样被释放)的情况下将它们删除或 GC。如果代码的其余部分需要 stdin 并且上面有其他重要事件阻止您执行上述操作,那么您基本上会以这种方式存在内存泄漏,并且实际上是孤立对象。
To get rid of df, v1 and v2 I need to call a destroy method on each of them. In terms of implementation this means that both the Filter and View methods need to keep the reference to the anonymous listener function they create as well as the observable and pass that to removeListener.
为了摆脱 df、v1 和 v2,我需要对它们中的每一个调用一个 destroy 方法。在实现方面,这意味着 Filter 和 View 方法都需要保留对它们创建的匿名侦听器函数以及可观察对象的引用,并将其传递给 removeListener。
On a side note, alternatively you can have an obserable that returns an index to keep track of listeners so that you can add prototyped functions which at least to my understanding should be much better on performance and memory. You still have to keep track of the returned identifier though and pass your object to ensure that the listener is bound to it when called.
顺便说一句,或者你可以有一个可观察的对象,它返回一个索引来跟踪监听器,这样你就可以添加原型函数,至少在我的理解中,它在性能和内存方面应该更好。您仍然必须跟踪返回的标识符并传递您的对象以确保侦听器在调用时绑定到它。
A destroy function adds several pains. First is that I would have to call it and free the reference:
销毁函数增加了一些痛苦。首先是我必须调用它并释放参考:
df.destroy();
v1.destroy();
v2.destroy();
df = v1 = v2 = null;
This is a minor annoyance as it's a bit more code but that is not the real problem. When I hand these references around to many objects. In this case when exactly do you call destroy? You cannot simply hand these off to other objects. You'll end up with chains of destroys and manual implementation of tracking either through program flow or some other means. You can't fire and forget.
这是一个小烦恼,因为它的代码有点多,但这不是真正的问题。当我将这些引用传递给许多对象时。在这种情况下,你到底什么时候调用销毁?您不能简单地将这些交给其他对象。您最终会通过程序流或其他方式获得破坏链和手动实施跟踪。你不能开火就忘记。
An example of this kind of problem is if I decide that View will also call destroy on df when it is destroyed. If v2 is still around destroying df will break it so destroy cannot simply be relayed to df. Instead when v1 takes df to use it, it would need to then tell df it is used which would raise some counter or similar to df. df's destroy function would decrease than counter and only actually destroy if it is 0. This sort of thing adds a lot of complexity and adds a lot that can go wrong the most obvious of which is destroying something while there is still a reference around somewhere that will be used and circular references (at this point it's no longer a case of managing a counter but a map of referencing objects). When you're thinking of implementing your own reference counters, MM and so on in JS then it's probably deficient.
此类问题的一个示例是,如果我决定 View 在销毁时也会在 df 上调用 destroy 。如果 v2 仍在销毁 df 将破坏它,因此销毁不能简单地传递给 df。相反,当 v1 需要 df 来使用它时,它需要告诉 df 它被使用,这会引发一些计数器或类似于 df 的计数器。df 的 destroy 函数会比 counter 减少,并且只有在它为 0 时才实际销毁。 这种事情增加了很多复杂性,并增加了很多可能出错的地方,其中最明显的是销毁某些东西,而在某处仍然有一个引用将被使用和循环引用(此时它不再是管理计数器的情况,而是引用对象的映射)。当您考虑在 JS 中实现您自己的引用计数器、MM 等时,它会“
If WeakSets were iterable, this could be used:
如果 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);
};
In this case the owning class must also keep a token reference to f otherwise it will go poof.
在这种情况下,拥有的类还必须保留对 f 的令牌引用,否则它将变得糟糕。
If Observable were used instead of EventListener then memory management would be automatic in regards to the event listeners.
如果使用 Observable 而不是 EventListener,那么对于事件侦听器而言,内存管理将是自动的。
Instead of calling destroy on each object this would be enough to fully remove them:
这足以完全删除它们,而不是在每个对象上调用 destroy :
df = v1 = v2 = null;
If you didn't set df to null it would still exist but v1 and v2 would automatically be unhooked.
如果您没有将 df 设置为 null,它仍然存在,但 v1 和 v2 将自动解除挂钩。
There are two problems with this approach however.
然而,这种方法有两个问题。
Problem one is that it adds a new complexity. Sometimes people do not actually want this behaviour. I could create a very large chain of objects linked to each other by events rather than containment (references in constructor scopes or object properties). Eventually a tree and I would only have to pass around the root and worry about that. Freeing the root would conveniently free the entire thing. Both behaviours depending on coding style, etc are useful and when creating reusable objects it's going to be hard to either know what people want, what they have done, what you have done and a pain to work around what has been done. If I use Observable instead of EventListener then either df will need to reference v1 and v2 or I'll have to pass them all if I want to transfer ownership of the reference to something else out of scope. A weak reference like thing would mitigate the problem a little by transferring control from Observable to an observer but would not solve it entirely (and needs check on every emit or event on itself). This problem can be fixed I suppose if the behaviour only applies to isolated graphs which would complicate the GC severely and would not apply to cases where there are references outside the graph that are in practice noops (only consume CPU cycles, no changes made).
问题之一是它增加了新的复杂性。有时人们实际上并不想要这种行为。我可以创建一个非常大的对象链,通过事件而不是包含(构造函数范围或对象属性中的引用)相互链接。最终一棵树和我只需要绕过根部并担心它。释放根将方便地释放整个事物。取决于编码风格等的两种行为都很有用,并且在创建可重用对象时,很难知道人们想要什么,他们做了什么,你做了什么,并且很难解决已经做过的事情。如果我使用 Observable 而不是 EventListener 则 df 将需要引用 v1 和 v2 或者如果我想将引用的所有权转移到超出范围的其他内容,则必须将它们全部传递。像弱引用这样的东西可以通过将控制从 Observable 转移到观察者来稍微缓解问题,但不会完全解决它(并且需要检查自身的每个发射或事件)。如果该行为仅适用于会使 GC 严重复杂化的孤立图,并且不适用于图外的引用实际上是 noops(仅消耗 CPU 周期,不进行任何更改)的情况,则可以解决此问题。
Problem two is that either it is unpredictable in certain cases or forces the JS engine to traverse the GC graph for those objects on demand which can have a horrific performance impact (although if it is clever it can avoid doing it per member by doing it per WeakMap loop instead). The GC may never run if memory usage does not reach a certain threshold and the object with its events wont be removed. If I set v1 to null it may still relay to stdout forever. Even if it does get GCed this will be arbitrary, it may continue to relay to stdout for any amount of time (1 lines, 10 lines, 2.5 lines, etc).
问题二是在某些情况下它是不可预测的,或者强制 JS 引擎按需遍历那些对象的 GC 图,这可能会产生可怕的性能影响(尽管如果它很聪明,它可以通过按每个成员执行它来避免每个成员执行它) WeakMap 循环代替)。如果内存使用率没有达到某个阈值并且带有事件的对象不会被删除,则 GC 可能永远不会运行。如果我将 v1 设置为 null,它仍然可能永远中继到标准输出。即使它确实被 GCed 这将是任意的,它可能会继续中继到 stdout 任何时间(1 行、10 行、2.5 行等)。
The reason WeakMap gets away with not caring about the GC when non-iterable is that to access an object you have to have a reference to it anyway so either it hasn't been GCed or hasn't been added to the map.
WeakMap 在不可迭代时不关心 GC 的原因是,要访问一个对象,您无论如何都必须引用它,因此它没有被 GC 处理或没有被添加到映射中。
I am not sure what I think about this kind of thing. You're sort of breaking memory management to fix it with the iterable WeakMap approach. Problem two can also exist for destructors as well.
我不确定我对这种事情的看法。您有点破坏内存管理以使用可迭代的 WeakMap 方法修复它。析构函数也可能存在问题二。
All of this invokes several levels of hell so I would suggest to try to work around it with good program design, good practices, avoiding certain things, etc. It can be frustrating in JS however because of how flexible it is in certain aspects and because it is more naturally asynchronous and event based with heavy inversion of control.
所有这些都会引发几个层次的地狱,所以我建议尝试通过良好的程序设计、良好的实践、避免某些事情等来解决它。 然而,在 JS 中它可能令人沮丧,因为它在某些方面是多么灵活,并且因为它更自然地异步和基于事件的大量控制反转。
There is one other solution that is fairly elegant but again still has some potentially serious hangups. If you have a class that extends an observable class you can override the event functions. Add your events to other observables only when events are added to yourself. When all events are removed from you then remove your events from children. You can also make a class to extend your observable class to do this for you. Such a class could provide hooks for empty and non-empty so in a since you would be Observing yourself. This approach isn't bad but also has hangups. There is a complexity increase as well as performance decrease. You'll have to keep a reference to object you observe. Critically, it also will not work for leaves but at least the intermediates will self destruct if you destroy the leaf. It's like chaining destroy but hidden behind calls that you already have to chain. A large performance problem is with this however is that you may have to reinitialise internal data from the Observable everytime your class becomes active. If this process takes a very long time then you might be in trouble.
还有另一种相当优雅的解决方案,但仍然有一些潜在的严重问题。如果您有一个扩展 observable 类的类,您可以覆盖事件函数。仅当事件添加到您自己时,才将您的事件添加到其他 observables。当所有事件都从您身上移除后,再从孩子身上移除您的事件。您还可以创建一个类来扩展您的 observable 类来为您执行此操作。这样一个类可以为空和非空提供钩子,因为你会观察自己。这种方法还不错,但也有问题。存在复杂性增加以及性能下降。您必须保留对所观察对象的引用。关键的是,它也不适用于叶子,但如果您破坏叶子,至少中间体会自毁。它' 就像链接 destroy 但隐藏在您已经必须链接的调用后面。然而,一个很大的性能问题是,每次您的类变为活动状态时,您可能都必须从 Observable 重新初始化内部数据。如果这个过程需要很长时间,那么你可能会遇到麻烦。
If you could iterate WeakMap then you could perhaps combine things (switch to Weak when no events, Strong when events) but all that is really doing is putting the performance problem on someone else.
如果您可以迭代 WeakMap,那么您也许可以组合一些东西(在没有事件时切换到 Weak,在有事件时切换到 Strong),但真正做的只是将性能问题放在其他人身上。
There are also immediate annoyances with iterable WeakMap when it comes to behaviour. I mentioned briefly before about functions having scope references and carving. If I instantiate a child that in the constructor that hooks the listener 'console.log(param)' to parent and fails to persist the parent then when I remove all references to the child it could be freed entirely as the anonymous function added to the parent references nothing from within the child. This leaves the question of what to do about parent.weakmap.add(child, (param) => console.log(param)). To my knowledge the key is weak but not the value so weakmap.add(object, object) is persistent. This is something I need to reevaluate though. To me that looks like a memory leak if I dispose all other object references but I suspect in reality it manages that basically by seeing it as a circular reference. Either the anonymous function maintains an implicit reference to objects resulting from parent scopes for consistency wasting a lot of memory or you have behaviour varying based on circumstances which is hard to predict or manage. I think the former is actually impossible. In the latter case if I have a method on a class that simply takes an object and adds console.log it would be freed when I clear the references to the class even if I returned the function and maintained a reference. To be fair this particular scenario is rarely needed legitimately but eventually someone will find an angle and will be asking for a HalfWeakMap which is iterable (free on key and value refs released) but that is unpredictable as well (obj = null magically ending IO, f = null magically ending IO, both doable at incredible distances).
当涉及到行为时,可迭代 WeakMap 也有直接的烦恼。我之前简要提到过具有作用域引用和雕刻的函数。如果我在构造函数中实例化一个将侦听器 'console.log(param)' 挂接到父级并且无法持久化父级的子级,那么当我删除对子级的所有引用时,它可以被完全释放,因为匿名函数添加到父级在子级中没有引用任何内容。这留下了如何处理 parent.weakmap.add(child, (param) => console.log(param)) 的问题。据我所知,key 很弱,但不是 value,所以 weakmap.add(object, object) 是持久的。不过,这是我需要重新评估的事情。对我来说,如果我处理所有其他对象引用,这看起来像是内存泄漏,但我怀疑实际上它基本上通过将其视为循环引用来管理它。匿名函数要么维护对由父作用域产生的对象的隐式引用,以保持一致性,浪费大量内存,要么您的行为因难以预测或管理的情况而异。我认为前者实际上是不可能的。在后一种情况下,如果我在一个类上有一个方法,它只接受一个对象并添加 console.log,即使我返回了该函数并维护了一个引用,当我清除对该类的引用时,它也会被释放。
回答by Cliff Hall
"A destructor wouldn't even help you here. It's the event listeners themselves that still reference your object, so it would not be able to get garbage-collected before they are unregistered."
“析构函数在这里甚至不会帮助你。事件侦听器本身仍然引用你的对象,所以在它们被取消注册之前它无法被垃圾收集。”
Not so. The purpose of a destructor is to allow the item that registered the listeners to unregister them. Once an object has no other references to it, it will be garbage collected.
不是这样。析构函数的目的是允许注册侦听器的项取消注册它们。一旦一个对象没有其他引用,它将被垃圾收集。
For instance, in AngularJS, when a controller is destroyed, it can listen for a destroy event and respond to it. This isn't the same as having a destructor automatically called, but it's close, and gives us the opportunity to remove listeners that were set when the controller was initialized.
例如,在 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() );
}
回答by Craig Hicks
If there is no such mechanism, what is a pattern/convention for such problems?
如果没有这样的机制,那么针对此类问题的模式/约定是什么?
The term 'cleanup' might be more appropriate, but will use 'destructor' to match OP
术语“清理”可能更合适,但会使用“析构函数”来匹配 OP
Suppose you write some javascript entirely with 'function's and 'var's.
Then you can use the pattern of writing all the functions code within the framework of a try/catch/finallylattice. Within finallyperform the destruction code.
假设您完全使用 'function's 和 'var's 编写了一些 javascript。然后你可以使用编写所有的模式function的框架内,S码try/ catch/finally格。在里面finally执行销毁代码。
Instead of the C++ style of writing object classes with unspecified lifetimes, and then specifying the lifetime by arbitrary scopes and the implicit call to ~()at scope end (~()is destructor in C++), in this javascript pattern the object is the function, the scope is exactly the function scope, and the destructor is the finallyblock.
与 C++ 风格的编写具有未指定生命周期的对象类,然后通过任意范围指定生命周期并隐式调用~()范围结束(~()在 C++ 中是析构函数)不同,在这个 javascript 模式中,对象是函数,范围正是函数作用域,析构函数是finally块。
If you are now thinking this pattern is inherently flawed because try/catch/finallydoesn't encompass asynchronous execution which is essential to javascript, then you are correct. Fortunately, since 2018 the asynchronous programming helper object Promisehas had a prototype function finallyadded to the already existing resolveand catchprototype functions. That means that that asynchronous scopes requiring destructors can be written with a Promiseobject, using finallyas the destructor. Furthermore you can use try/catch/finallyin an async functioncalling Promises with or without await, but must be aware that Promises called without await will be execute asynchronously outside the scope and so handle the desctructor code in a final then.
如果你现在想这种模式固有的缺陷,因为try/ catch/finally不包括异步执行这是JavaScript的必要,那么你是正确的。幸运的是,自2018异步编程辅助对象Promise已经有了一个原型功能finally添加到现有resolve和catch原型功能。这意味着需要析构函数的异步作用域可以用Promise对象编写,finally用作析构函数。此外,你还可以使用try/ catch/finally在async function调用Promise带或不带小号await,但必须认识到,Promise在没有 await 的情况下调用的 s 将在作用域外异步执行,因此在 final 中处理析构函数代码then。
In the following code PromiseAand PromiseBare some legacy API level promises which don't have finallyfunction arguments specified. PromiseCDOES have a finally argument defined.
在下面的代码中PromiseA,PromiseB是一些没有finally指定函数参数的遗留 API 级别承诺。 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 */ }
}
I am not advocating that every object in javascript be written as a function. Instead, consider the case where you have a scope identified which really 'wants' a destructor to be called at its end of life. Formulate that scope as a function object, using the pattern's finallyblock (or finallyfunction in the case of an asynchronous scope) as the destructor. It is quite like likely that formulating that functional object obviated the need for a non-function class which would otherwise have been written - no extra code was required, aligning scope and class might even be cleaner.
我并不是提倡将 javascript 中的每个对象都写成一个函数。相反,请考虑这样一种情况:您确定了一个真正“想要”在生命周期结束时调用析构函数的范围。将该作用域表述为函数对象,使用模式的finally块(或finally异步作用域中的函数)作为析构函数。很可能制定该功能对象消除了对非功能类的需求,否则该类将被编写 - 不需要额外的代码,对齐范围和类甚至可能更清晰。
Note: As others have written, we should not confuse destructors and garbage collection. As it happens C++ destructors are often or mainly concerned with manual garbage collection, but not exclusivelyso. Javascript has no need for manual garbage collection, but asynchronous scope end-of-life is often a place for (de)registering event listeners, etc..
注意:正如其他人所写,我们不应该混淆析构函数和垃圾收集。碰巧的是,C++ 析构函数经常或主要与手动垃圾收集有关,但并非完全如此。Javascript 不需要手动垃圾收集,但异步范围的生命周期结束通常是(取消)注册事件侦听器等的地方。

