大家好,我在做一个手动释放 js 对象内存的测试,发现无法通过设置对象为 null
释放对象
下面是代码:
window.onload = function () {
function Test(dom) {
this.dom = dom;
this.str = '';
var self = this;
var i = 0;
this.funA = function () {
self.str = '123';
}
this.dom.addEventListener('click', function(){}, false);
}
var p1Obj = document.getElementsByClassName('p1')[0];
var myTest = new Test(p1Obj);
//通过点击p2释放myTest对象
var p2Obj = document.getElementsByClassName('p2')[0];
p2Obj.onclick = function(){
myTest = null;
}
}
如果将 funA
中的 self.str
换成 this.str
则点击 p2
可以释放 myTest
资源。
我的理解:
p1
的 click
监听事件中没有调用 funA
函数,没有形成闭包,为何会无法释放 Test
资源?
funA
中的 self
和 this
应该是同一个对象,为什么用 self
无法释放而用 this
则可以?
请大神指导,谢谢!
ringa_lee2017-04-11 10:38:04
window.onload = function () {//onloadCallbackFun
function Test(dom) {//TestFun
this.dom = dom;//[A]
this.str = '';//[B]
var self = this;//[C]
var i = 0;
this.funA = function () {//[D]
self.str='123';//[E]
//this.str='123';//[E1]
}
this.dom.addEventListener('click', function () { }, false); //[E2] p1OnClickCallback
}
var p1Obj = document.getElementsByClassName('p1')[0];
var myTest = new Test(p1Obj);//[F]
//通过点击p2释放myTest对象
var p2Obj = document.getElementsByClassName('p2')[0];
p2Obj.onclick = function () { //p2OnClickCallback
myTest = null;//[G]
}
}
之前尝试的回答了一些,发现很多地方无法解释的通,查了些资料整理下思路,再回答下~
这里涉及的JS的GC回收,闭包等,我们先理清几个概念
关于this:
this变量是函数的执行上下文,在函数被执行时,才会有值,绑定到这个函数的调用者。通过Function对象的apply和call方法可以手工调整this的指向
var objA={
funName:"objA fun",
myFun:function(){
console.log(this.funName);
}
};
objA.myFun();//objA fun
var anotherFun=objA.myFun;
anotherFun();//undefined
anotherFun.call({funName:'carzy fun'});//carzy fun
关于闭包:
1.闭包是在函数声明的时候生成的,而非函数执行时;是对原有函数词法作用域的扩展。闭包在一个函数内声明函数时生成,确定的说是在外层函数被执行时;在JS中定义的任何函数都可以认为有一个闭包,直接在全局作用域下定义的函数就有全局作用域闭包,能访问全局作用域下的所有变量。
2.在同一个函数内声明的多个内部函数闭包作用域绑定的外层函数的变量是相同的,但是形成的闭包是不同的,例如内部函数A使用了外部函数的变量outerV1,那么就算内部函数B的函数体没有使用任何外部函数的变量,内部函数B的闭包和内部函数A的闭包绑定的变量是相同的-都会绑定outerV1。
3.并不是外部函数的所有局部变量都会出现在内部函数的闭包中,是内部函数使用到的外部函数变量的并集
4.并不是在在一个外层函数中声明的内部函数都会形成闭包,以下几种情况下JS引擎会对其进行优化,避免生成
A.当声明一个内部函数,但是这个函数没有被引用,或只是被一个局部变量引用,那么这个函数的闭包在外层函数执行完后会被丢弃
B.当内部声明的所有函数没有使用到任何外层函数的变量,那么聪明的JS引擎(例如Chrome V8)将优化闭包-消除闭包的生成
关于JS GC垃圾回收
1.现代的JS引擎普遍采用标记-清除算法来执行自动GC回收,而非引用计数器算法-在遇到对象循环引用时会有障碍
2.GC的回收从GC Root开始遍历访问对象,能被访问到的对象认为是可触达的(reachable)的。unreachable的对象将被GC回收掉
3.JS中的GC Root有浏览器环境下window全局对象,document对象及global对象(nodeJS环境下)
好了,我们回到问题
onloadCallbackFun函数声明中,定义了p1Obj,p2Obj,myTest变量及TestFun函数对象。
当[F]行代码调用时,生成了一个以Test为构造函数的实例对象[instanceTest]
,[A] [B] [C]中的this和myTest都指向这个实例对象
声明的funA匿名函数和p1OnClickCallback匿名函数有相同的闭包作用域变量self-指向[instanceTest]
,并且p1 dom对象绑定了p1OnClickCallback匿名函数
p2OnClickCallback匿名函数生成闭包,能够访问外层还是的myTest变量-指向[instanceTest]
在用户点击p2,触发p2OnClickCallback函数执行前,JS GC机制将不能回收[instanceTest]
,因为以p1为根对象出发,[instanceTest]
是可以被触达的p1-p1OnClickCallback->闭包对象->self->[instanceTest]
,从p2为根对象出发,也是如此p2-p2OnClickCallback->闭包对象->myTest->[instanceTest]
``
当用户点击p2后,myTest被设置为null,只是说myTest和[instanceTest]
之间的联系被切断,[instanceTest]
依然存在。此时执行GC尝试回收[instanceTest]
,
从p2为根对象出发,[instanceTest]
不能被触达,但是 p1-p1OnClickCallback->闭包对象->self->[instanceTest]
依旧成立,GC对[instanceTest]
无能为力
把[E2]行注释掉,[instanceTest]
就无法被触达,GC就可以回收它
如果[E]行换成[E1]行,那么导致的结果是:funA匿名函数和p1OnClickCallback匿名函数都被JS引擎优化为不生成闭包,也就是p1对[instanceTest]
不具有引用关系
p2被点击,回调函数触发后,无论从p1还是p2都无法触达[instanceTest]
,GC就会回收它
换几种写法看看GC情况
A 代码如下写,GC回收[instanceTest]
,无法触达
window.onload = function () {//onloadCallbackFun
function Test(dom) {//TestFun
this.dom = dom;//[A]
this.str = '';//[B]
var self = this;//[C]
var i = 0;
this.funA = function () {//[D]
self.str='123';//[E]
//this.str='123';//[E1]
}
}
var p1Obj = document.getElementsByClassName('p1')[0];
var myTest = new Test(p1Obj);//[F]
}
B 加入setTimeout定时器
window.onload = function () {//onloadCallbackFun
function Test(dom) {//TestFun
this.dom = dom;//[A]
this.str = '';//[B]
var self = this;//[C]
var i = 0;
this.funA = function () {//[D]
self.str='123';//[E]
//this.str='123';//[E1]
}
setTimeout(function(){
//console.log(self);
},5000);
}
var p1Obj = document.getElementsByClassName('p1')[0];
var myTest = new Test(p1Obj);//[F]
}
GC无法回收[instanceTest]
,因为setTimeout作为全局对象下的一个函数,可以触达[instanceTest]
.
在定时器执行完毕后,setTimeout函数和定时器回调函数解除绑定,[instanceTest]
将无法被触达而被回收.
这里要注意下,如果把//console.log(self);
注释去掉,[instanceTest]
将无法被回收,这是因为console也是一个根对象,window->console->self,self可达
C 全局变量引用
<script>
var myTest;
window.onload = function () {//onloadCallbackFun
function Test(dom) {//TestFun
this.dom = dom;//[A]
this.str = '';//[B]
var self = this;//[C]
var i = 0;
this.funA = function () {//[D]
self.str='123';//[E]
//this.str='123';//[E1]
}
}
var p1Obj = document.getElementsByClassName('p1')[0];
myTest= new Test(p1Obj);//[F]
}
</script>
GC无法回收[instanceTest]
,myTest作为全局变量将保证触达[instanceTest]
,除非在其它地方设置myTest=null;
最后Chrome DevTool profile
真是个好的检测内存使用的好工具!
ringa_lee2017-04-11 10:38:04
换个浏览器说不定就可以了。
1.GC
什么时候运行,完全取决于浏览器自身的,比如低版本的 IE ,只有对象个数大于 6000 才会运行 GC。
2.GC释放的是那些绝对用不到的资源,当你的代码写的不好或是浏览器优化做的不好的话,短时间内 GC 不一定能够意识要这个资源已经访问不到了。
高洛峰2017-04-11 10:38:04
释放资源?容我先问个问题,你是如何断定自己可以释放资源的,譬如你这句话“如果将funA中的self.str换成this.str则点击p2可以释放myTest资源”,你通过什么判断释放成功的?