Home  >  Q&A  >  body text

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 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 资源。

我的理解:

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

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

    请大神指导,谢谢!

PHP中文网PHP中文网2772 days ago946

reply all(4)I'll reply

  • ringa_lee

    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真是个好的检测内存使用的好工具!

    reply
    0
  • ringa_lee

    ringa_lee2017-04-11 10:38:04

    换个浏览器说不定就可以了。
    1.GC 什么时候运行,完全取决于浏览器自身的,比如低版本的 IE ,只有对象个数大于 6000 才会运行 GC。
    2.GC释放的是那些绝对用不到的资源,当你的代码写的不好或是浏览器优化做的不好的话,短时间内 GC 不一定能够意识要这个资源已经访问不到了。

    reply
    0
  • 高洛峰

    高洛峰2017-04-11 10:38:04

    释放资源?容我先问个问题,你是如何断定自己可以释放资源的,譬如你这句话“如果将funA中的self.str换成this.str则点击p2可以释放myTest资源”,你通过什么判断释放成功的?

    reply
    0
  • 黄舟

    黄舟2017-04-11 10:38:04

    将完整页面跑一下,用 chrome devtools 分析一下:

    JS内存泄漏排查方法——Chrome Profiles

    reply
    0
  • Cancelreply