谈一谈Javascript内存释放那点事

Javascript语言有自己的一套内存回收机制,一般情况下局部变量和对象使用完就会被系统自动回收,无需我们理会。但是碰到闭包的情况这些变量和对象是不会被回收的,对于普通的web站点,页面刷新或跳转这些内存也会被回收。如果是单页web站点,页面切换及数据请求都是通过ajax无刷新机制实现的,页面资源无法自动回收,时间长了会严重影响性能,造成内存泄漏甚至页面崩溃直接退出,这时候手动释放不用资源就非常必要了,包含删除dom、释放对象等,这篇文章介绍如何释放JS对象。


一、在此之前我们需要学会使用Chrome的内存分析工具来查看页面各个对象的内存占用情况

1、在开发者工具中选中Profiles,选择Take Heap Snapshot,点击Take Snapshot按钮

谈一谈Javascript内存释放那点事_第1张图片

2、选中生成的Heap Snapshot报表,在右边输入要查询的对象

谈一谈Javascript内存释放那点事_第2张图片


来看看下面几个例子【注:例子实现的功能实际意义,只是为了展示今天要讲的东西】:

html部分:



        div1
   

   

        div2
   

二、没有形成闭环,创建的对象使用完后自动销毁或手动设为null销毁

1、系统自动回收Test对象

window.οnlοad=function(){

  function Test(Dom)
  {
  this.Dom=Dom;
 this.str='';
  }
  
  var div1=document.getElementsByClassName('div1')[0];
  var myTest=new Test(div1); 
  
  
 }

2、在div1上加了click事件监听

window.οnlοad=function(){

  function Test(Dom)
  {
  this.Dom=Dom;
 this.str='';
  this.dom.addEventListener('click', function () { }, false);
 }
  
  var div1=document.getElementsByClassName('div1')[0];
  var myTest=new Test(div1); 
  
  
 }

监听函数中没有对tes对象变量的引用,没有形成闭包,故代码执行完后会自动销毁


3、使用了延时执行

window.οnlοad=function(){

  function Test(Dom)
  {
   this.Dom=Dom;
  this.str='';
   var self=this;
   var timer = window.setTimeout(function () {self.str='123';},1000)
 }
  
  var div1=document.getElementsByClassName('div1')[0];
  var myTest=new Test(div1); 
   
 }

使用了window.setTimeout延迟,执行函数中有引用对象的属性,但引用是一次性的,没有形成闭环


4、使用了定时器

window.οnlοad=function(){

  function Test(Dom)
  {
   this.Dom=Dom;
  this.str='';
   var self=this;
   var timer = window.setInterval(function () {}, 1000);
 }
  
  var div1=document.getElementsByClassName('div1')[0];
  var myTest=new Test(div1); 
   
 }
循环执行函数中没有对对象属性的引用,没有形成闭环

三、形成了闭环,系统无法自动回收对象资源,也无法手动将对象设为null销毁,只能删除对象属性的引用,再通过手动将对象设置为null销毁

1、dom的监听事件中有对对象属性的引用

window.οnlοad=function(){

  function Test(Dom)
  {
  this.Dom=Dom;
 this.str='';
  this.dom.addEventListener('click', function () {self.str='123'; }, false);
 }
  
  var div1=document.getElementsByClassName('div1')[0];
  var myTest=new Test(div1); 
   
 }

为了能够移除监听事件,我们将监听函数单独定义。再Test函数增加一个原型方法Destroy,作用就是删除dom的click监听事件,供外部调用。

给div2增加一个click事件,调用Test的destroy方法销毁Test对象

window.οnlοad=function(){

  function Test(Dom)
  {
  this.Dom=Dom;
 this.str=''; 
  this.A=function(){
      this.str='123';
  }
 this.dom.addEventListener('click', self.A, false);
 }

Test.prototype.destroy = function () {
  var self=this;
  this.dom.removeEventListener('click',self.A, false);
 }
 var div1=document.getElementsByClassName('div1')[0]; var myTest=new Test(div1);

//点击div2,销毁Test对象
 var div2Obj = document.getElementsByClassName('div2')[0];
  div2Obj.onclick = function () {             
    myTest.destroy();
    myTest = null;
   }  }

 点击div2后通过内存分析发现Test对象消失了,说明已经销毁了 
  

谈一谈Javascript内存释放那点事_第3张图片

2、window.setInterval事件中有对对象属性的引用

window.οnlοad=function(){

  function Test(Dom)
  {
  this.Dom=Dom;
 this.str='';
  this.dom.addEventListener('click', function () {self.str='123'; }, false);
 }
  
  var div1=document.getElementsByClassName('div1')[0];
  var myTest=new Test(div1); 
   
 }

我们增加一个对象属性timer来接收window.setInterval的句柄,在destory方法中清除定时器。

window.οnlοad=function(){

  function Test(Dom)
  {
  this.Dom=Dom;
 this.str=''; 
  this.timer=null;
 this.timer = window.setInterval(function () { self.str = '123';}, 1000);
 }

  Test.prototype.destroy = function () {
  window.clearInterval(this.timer);
 }

 var div1=document.getElementsByClassName('div1')[0];  
  var myTest=new Test(div1); 

    //点击div2,销毁Test对象
   var div2Obj = document.getElementsByClassName('div2')[0];
   div2Obj.onclick = function () {              
    myTest.destroy();
    myTest = null;
   }   
}
点击div2后通过内存分析发现Test对象消失了,说明已经销毁了


从上面的例子可以看出,想手动释放含有闭包的对象时,必须先将引用对象属性的事件删除,然后设置为null方可消耗对象。这种事件一般是可以多次执行的,如原生事件的监听,定时器。一般比较有名较完善的插件都有带销毁资源方法,如iscroll插件,里面就有一个destroy原型方法,它里面也就是移除事件监听和删除定时器。大家可以去看看源码。


写在最后

单页web的性能优化之路任重而道远,本文只是谈了一下如何释放创建的对象,其实还有很多可以优化的点:dom优化、动画优化等。希望可以和大家一起讨论。


你可能感兴趣的:(Javascript)