![](/style/images/good.png)
![](/style/images/bad.png)
js 的类有没有析构函数,如何进行资源释放??
source link: https://www.v2ex.com/t/799592
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
比如,考虑以下场景,有一些学生类 Student,以及一个图书馆类 Library 。
1. 开始创建了若干学生 Student 对象,然后做了一些操作,向 Library 借了几本书。
2. 过了一会,有些学生对象做了一些其它操作(比如上课、睡觉,但就是没有还书),然后自动释放了(离开了变量作用域,生存周期结束了)。
3. 此时虽然说 Student 自动释放了,但是还书操作没有显式调用,造成书籍未还。
所以,如果 Student 类有析构函数,我就可以在析构函数中进行还书等的资源释放。正规语言都有析构函数,现在怎么处理,?
第 1 条附言 · 23 小时 34 分钟前
第 2 条附言 · 23 小时 33 分钟前
第 3 条附言 · 19 小时 42 分钟前
James369 1 天前
我再举个例子,比如 Student 在 Canvas 上画了一个圆,但是 Student 释放的时候,它不会做擦除圆的操作。
NewConn 23 小时 57 分钟前 1
楼主显然问的是一个业务问题,不是 js 或者 v8 的 GC 问题
楼主问的是,对象销毁时,业务上需要做一个业务操作;类推到构造函数就是,对象创建时,业务上做一些业务操作
pinkSlime 23 小时 49 分钟前
私以为 构析函数仅仅与内存相关,关闭句柄啊之类的。“书”这个对象自有 GC 来释放,而“还书”这明明是个逻辑操作,应该显式调用更好吧。
Vegetable 23 小时 47 分钟前
kop1989 23 小时 40 分钟前 7
第二段转头就说“书未还”(业务问题)了。
楼主真的想明白你到底要干什么了么。
1 、析构函数你用来处理业务?
2 、有回收机制的语言怎么会有狭义的、原教旨主义的析构函数?
3 、建议你去了解一下带有 GC 机制语言的“销毁事件”的特点与注意事项。
shawckzh 22 小时 23 分钟前 1
LZ 要是觉得显式调用麻烦,建议自己写个简单的 defer,就是函数退出时隐式调用呗,wrap 一下就行
yannxia 21 小时 57 分钟前
推荐都是 close hook
chairuosen 21 小时 41 分钟前
ysc3839 20 小时 19 分钟前
7gugu 20 小时 10 分钟前
qrobot 19 小时 55 分钟前
例如, 我在 Student 释放的同时,去创建 Student 。 那么这个对象将永远无法真正的释放。 例如在释放的时候进行执行大量的逻辑。 显然这是非常影响 gc 的性能行为。
不过你可以在要销毁的时候。 执行其他情况, 你不应该依赖 gc
例如我记得 java 的 gc, 只是做一个垃圾标识, 至于什么时候进行清理,要看 gc 的心情。
例如在 JS 中, 你应该这样
```js
let student = new Student()
// 标记需要回收
delete student;
// free 是你自己自定义的方法,用来编写逻辑,释放你的图片信息,以及擦除圆的操作。
free(student)
```
qrobot 19 小时 43 分钟前
所有的语言,无论高低都需要关心内存问题
1. 分配你所需要的内存
2. 使用分配到的内存(读、写)
3. 不需要时将其释放\归还
有一些语言是需要开发者手动明确进行处理的,例如 rust c/c++ , 而有一些是隐含帮你处理例如 Java Go 等等。 但是无论那种你都需要关注内存分配,以及销毁的问题。
例如在 Java 中 常见的内存泄漏
```
Static Vector v = new Vector(10);
for (int i = 0; i < 100; i++) {
Object o = new Object();
v.add(o);
o = null;
}
```
无论是那种语言,开发都需要关注内存回收的机制,以及内存回收的问题。如果不去关注这些。 很容易出一些内存泄漏的问题。
2i2Re2PLMaDnghL 19 小时 26 分钟前
GC 本来发生时间都不确定,JS 某些实现根本不进行任何 GC,快速 fork 一份运行完整个进程一并销毁,不需要任何 GC
本来语言层面上就没保证的事情。
听说,Scheme 语言设计上没有保证函数的参数是从左到右顺次求值的,所以存在一些 Scheme 实现特意不按这个顺序求值,并以取笑搞错了的人为乐
zjsxwc 19 小时 3 分钟前
In Swift, destructors are not required, as the memory deallocation is abstracted away and done automatically. However, they are available and known as “deinitializers”, to perform any cleanup that needs to be done just prior to actual deallocation of the object. Deinitializers are optional, and there can be one at most in a class.
In our car example, before we send it to the junkyard, we might want to un-register the vehicle’s license and cancel the insurance:
class Car {
//properties
init(model:String, color:String, vin:Int) {
// init code
}
deinit {
unRegisterLicense() // some function that un-registers the license
cancelInsurance() // some function that cancels the insurance policy
}
}
chairuosen 18 小时 48 分钟前 1
var Obj = function(name){
this.name = name;
}
Obj.prototype.destroy = function(){
console.log('destroy', this.name);
}
var store = {};
var myObj = new Obj('123');
Object.defineProperty(store, 'myObj', {
get: function(){
return myObj;
},
set: function(newVal){
if(!newVal){
myObj && myObj.destroy();
}
myObj = newVal;
}
});
setTimeout(function(){
store.myObj = null;
},100)
libook 18 小时 14 分钟前 4
关于内存泄漏:
要用内存的时候需要申请,用完了要释放,这些操作仅存在于可以直接操作内存的语言,如 C 、C++,申请了内存忘了释放会导致内存泄漏,在一些重业务轻资源管理的场景下,人们发明了一些自动释放内存的语言,如 Java 、C#、JS 、Go,大多是采用 GC 方案,也有采用其他方案的如 Rust 的 safe 的代码采用 Ownership 方案。
但不是说有了自动释放内存的方案就可以完全避免“内存泄漏”了,没有遵照 GC 、Ownership 的规则来使用同样会导致内存泄漏,比如持续创建对象,但又让对象持续被引用,GC 检测到对象被引用则不释放内存,可能业务上已经用不到这些对象了,但对象依然越来越多,直到用满内存。
所以对于 JS 来说,绝大多数时候都不需要考虑内存相关的问题,特殊情况下要释放内存也不需要借助析构函数之类的特性,而是随时都可以直接为 GC 创造条件诱导其释放内存,比如打破引用关系,比如使用 WakeMap 。
关于析构函数:
C 语言也没有析构函数(非 OO 连构造函数都没有)。
析构函数( destructor )是面向对象编程思想里的一个概念,存在于 RAII 方式的场景,RAII 方式的特点是资源的分配和释放的时机是被精准控制的。JS 是使用 GC 来释放内存,GC 的释放时机是不确定的,而且假如对象不满足 GC 释放要求的时候强行释放可能会导致有些资源被提前释放了,从而使得引用关系树紊乱,这也是 GC 要避免的问题之一。你可以使用 FinalizationRegistry 来监听对象被 GC 回收的事件,某种意义上来说可以算是一种“析构函数”,但这个与其他很多语言的方式有很大区别,开发者无法预料这个事件什么时候发生,但如果契合你的需求场景,也是可以用的。同时不同 JS 引擎对于 GC 的实现也可能有差异,过度关心内存可能会导致程序兼容性下降,特别是适配问题很烦人的浏览器端开发场景。
关于释放文件句柄、网络连接、锁:
这些资源跟内存是完全不同的,确实是需要手动释放的,但不是说必须用析构函数,C 语言没有析构函数,除了要手动释放内存以外,也要释放这些非内存资源。
JS 也一样,相关 API 也都提供了诸如 filehandle.close()、writableStream.end()之类的方法,但并不一定需要在对象被回收的时候释放。用 C++之类的语言的时候,有析构函数可以用来释放内存,于是当其他资源生命周期和对象一样的时候,可以一起放在析构函数里释放;但并不是说释放资源一定要在析构函数里做,C++也可以不在析构函数里释放这些资源。人们在用 JS 的时候,压根不会去考虑内存释放的问题,所以也压根不会考虑使用析构函数,那么只要是符合业务流程定义的位置,都可以显式调用方法释放相应的资源,你可以直接在一个代码流程中释放,也可以监听相应事件来释放。
如果程序中没有释放资源,当进程退出的时候( Node.js 支持多进程编程),操作系统也会回收所有资源,包括内存、文件句柄、网络连接等等。
最后:
1. 不知道题主是否明确清楚自己是否真的需要析构函数来释放这些资源,如果只是从 C++之类的开发精力带过来的习惯,那大可不必,JS 和其他语言有很多不同点,很多时候在一个语言上硬套另一个语言的思路会步履维艰,最终会觉得这个语言很难用。不如拥抱这个语言自己的开发习惯,可以参照一些主流项目的代码,看看大家一般会怎么做。
2. 语言表达的最终目标是让别人准确理解自己的意思,所以使用概念的时候还是要多加斟酌的,看是不是自己想表达的那个意思,造成误解可能会有其他连带的麻烦。
GeruzoniAnsasu 17 小时 52 分钟前 2
是的大量带 GC 的语言都无法 RAII,他们都必须手动管理资源释放。虽然 golang 之类的语言有 defer,但逻辑跟 RAII 完全不同,是两套思维方式。
他们这些“脚本语言”的做法:
池化(本质上是由分派器管理资源)
依赖注入(本质上是剥离资源申请释放的依赖,放到第三方)
使用 promise/future (本质上是模仿 RAII )
完全抛弃状态,使用函数式
是不是很熟悉,「设计模式」跟语言特性是有很大相关性的
fyxtc 13 小时 41 分钟前
就拿你举的例子来说,如果不是通过 new 创建的,那么离开作用域自动析构,那么你就应该在离开作用域的时候调用还书,很直接的线性关系。如果是 new 出来或者有存在其他地方的引用的,那么肯定是在之后某个通知下触发学生销毁了,那么就应该在那里调用还书,而不是依赖析构。
ragnaroks 13 小时 31 分钟前
btw,现在也没有 OnDestory 的事件,但是可以实现 IDispose 接口
namelosw 10 小时 51 分钟前
你这说得义正辞严的。我猜你用过的正规语言只有一个哈哈哈哈。
连 C 都没有不用析构函数,Rust 都不怎么用析构函数,更别提 Java 和 JavaScript 了。
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK