javascript - js无法释放对象内存

查看:436
本文介绍了javascript - js无法释放对象内存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问 题

大家好,我在做一个手动释放 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 div1Obj = document.getElementsByClassName('div1')[0];
    var myTest = new Test(div1Obj);
    
    //通过点击div2释放myTest对象 
    var div2Obj = document.getElementsByClassName('div2')[0];
    div2Obj.onclick = function(){                
        myTest = null;
    }
}

如果将 funA 中的 self.str 换成 this.str 则点击 div2 可以释放 myTest 资源。

我的理解:

  1. div1click 监听事件中没有调用 funA 函数,没有形成闭包,为何会无法释放 Test 资源?

  2. funA 中的 selfthis 应该是同一个对象,为什么用 self 无法释放而用 this 则可以?

    请大神指导,谢谢!

解决方案

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] div1OnClickCallback
        }

        var div1Obj = document.getElementsByClassName('div1')[0];
        var myTest = new Test(div1Obj);//[F]
        //通过点击div2释放myTest对象
        var div2Obj = document.getElementsByClassName('div2')[0];
        div2Obj.onclick = function () { //div2OnClickCallback
            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函数声明中,定义了div1Obj,div2Obj,myTest变量及TestFun函数对象。
当[F]行代码调用时,生成了一个以Test为构造函数的实例对象[instanceTest],[A] [B] [C]中的this和myTest都指向这个实例对象
声明的funA匿名函数和div1OnClickCallback匿名函数有相同的闭包作用域变量self-指向[instanceTest],并且div1 dom对象绑定了div1OnClickCallback匿名函数
div2OnClickCallback匿名函数生成闭包,能够访问外层还是的myTest变量-指向[instanceTest]

在用户点击div2,触发div2OnClickCallback函数执行前,JS GC机制将不能回收[instanceTest],因为以div1为根对象出发,[instanceTest]是可以被触达的
div1-div1OnClickCallback->闭包对象->self->[instanceTest],从div2为根对象出发,也是如此div2-div2OnClickCallback->闭包对象->myTest->[instanceTest]``

当用户点击div2后,myTest被设置为null,只是说myTest和[instanceTest]之间的联系被切断,[instanceTest]依然存在。此时执行GC尝试回收[instanceTest],
从div2为根对象出发,[instanceTest]不能被触达,但是 div1-div1OnClickCallback->闭包对象->self->[instanceTest]依旧成立,GC对[instanceTest]无能为力

把[E2]行注释掉,[instanceTest]就无法被触达,GC就可以回收它

如果[E]行换成[E1]行,那么导致的结果是:funA匿名函数和div1OnClickCallback匿名函数都被JS引擎优化为不生成闭包,也就是div1对[instanceTest]不具有引用关系
div2被点击,回调函数触发后,无论从div1还是div2都无法触达[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 div1Obj = document.getElementsByClassName('div1')[0];
        var myTest = new Test(div1Obj);//[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 div1Obj = document.getElementsByClassName('div1')[0];
        var myTest = new Test(div1Obj);//[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 div1Obj = document.getElementsByClassName('div1')[0];
       myTest= new Test(div1Obj);//[F]
    }
</script>

GC无法回收[instanceTest],myTest作为全局变量将保证触达[instanceTest],除非在其它地方设置myTest=null;

最后Chrome DevTool profile真是个好的检测内存使用的好工具!

这篇关于javascript - js无法释放对象内存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆