也议 js闭包和ie内存泄露原理

可以, 但小心使用.

闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档.

有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能会产生循环引用, 进一步导致内存泄漏. 比如下面的代码:

function foo(element, a, b) {
element.onclick = function() { /* uses a and b */ };
}

这里, 即使没有使用 element, 闭包也保留了 elementa 和 b 的引用, . 由于 element 也保留了对闭包的引用, 这就产生了循环引用, 这就不能被 GC 回收. 这种情况下, 可将代码重构为:

function foo(element, a, b) {
element.onclick = bar(a, b);
} function bar(a, b) {
return function() { /* uses a and b */ }
}
————— 谷歌js编码规范
这是谷歌js编码规范里的。"闭包",是个绕不开的话题,我查阅了不少资料,各种解释都有,关于为什么会造成内存泄露,也都介绍的很晦涩,没有把原理讲透,在这里,我就这两个问题详细讲一下。
首先讲闭包。闭包简而言之,就是一个函数(fa),的内部函数(fb)被fa外的变量引用,就形成了一个闭包;下面给出两种事例:
1)
function fa() {
function fb() {
alert("hello word");
}
return fb;
}
var myfun = fa();
myfun();
2)
function fa() {
var e = document.getElementById("id");
e.event = function () {
alert("hello word");
};
}
    这两种形式的形成闭包的机制不同,一个通过一个return 返回这个内部函数,从而被包外引用,而另一个则是通过 e,这个docment这个宿主对象的事件而完成外部引用。这两种形式形成的结果就是fa这个函数的的内部函数可以被fa的外部变量所引用,这就形成了闭包。
闭包讲到这里我想大家琢磨一下应该很清楚了,下面我们来分析下这个内存泄露是怎么形成的。很多资料都说循环引用,IE的计数式的垃圾回收机制,但我相信,这些概念很模糊,到底是怎么们回事,我们下面详细来剖析。

     垃圾回收机制现在很成熟了,但早期的IE版本里(ie4-ie6),对宿主对象(也就是document对象)采用是计数的垃圾回收机制,闭包导致内存泄露的一个原因就是这个算法的一个缺陷。循环引用会导致没法回收,这个循环引用只限定于有宿主对象参与的循环引用,而js对象之间即使形成循环引用,也不会产生内存泄露,因为对js对象的回收算法不是计数的方式。

     首先我们明确下内存泄露的概念:内存里不能被回收也不能被利用的空间即为内存泄露。为什么不能被回收呢?不符合内存回收的算法;为什么不能被利用呢?在栈上没有指向他的指针。在这里我简单的讲一下堆和栈的关系:
    function fa() {
var o = new Object();
}
fa();
我们看这段代码执行的时候发生了什么

我们看到,栈上只是存了一个指针,指针就是堆上对象的的地址;我们的程序通过这个指针句可以操作堆上的对象。栈上的这个指针是自动管理的,当函数退出后,就销毁了;这样程序就在没办法访问到堆上的这个对象了,而堆上的这个对象这个时候就会被我们的GC自动回收了;如果回收不了,就是内存泄露了。

  讲到这里大家对内存泄露应该是有所了解了,对于计数回收方式大家查下资料,相信大家根据上面讲的应该可以看明白了,这里不再详细描述。下面我们着重描述下内存泄露的原因,大家先看下面的代码:
  function fa() {
var a = "hello word";
return function () {
alert(a);
}
}
var o = fa();
o();

这段代码输出hello word,这说明什么?说明在堆上的”hello word‘ 没有被回收,什么原因?因为o这个函数还要引用这个变量。下面我们用计数的GC方式来逐句分析程序的代码

在堆上有两个对象 一个是 hello word 我们叫做O1,匿名函数function(){alert(a);}我们叫做O2

     当执行
var a = "hello word"; 的时候 O1的计数为1;
当执行
return function () {
            alert(a);
}
的时候 O2的计数变为1;
当执行完fa这个函数后 栈上的 var a 会被销毁,同是他指向的对象计数减1,这样问题就来了,这样O1的计数变为0了,那不被gc回收了嘛?怎么还会输出"hello word"?
原来在执行
return function () {
            alert(a);
}
这个函数的时候,为了保持函数对这个变量的引用,在这个匿名函数的作用域链上加了一个对O1的引用,这样 其实 O1的计数在变成了2,在a被销毁后,O1减变成了1而不是0.
那么O1时候被回收呢?当O2被回收的时候。O2什么时候被回收呢?当指向他的var o 从栈上消失的时候。
 好,讲到这里,原理我们讲完了下面我们就看下
function fa() {
var e = document.getElementById("id");
e.event = function () {
alert("hello word");
};
}

这段代码为什么会造成内存泄露

var e = document.getElementById("id"); 执行这段代码的时候 右边(O1)的对象计数变为了1

执行这段代码的时候

e.event = function () {
            alert("hello word");
};

匿名函数(O2)的计数变了1;对象O1的计数变了2;

当函数fa执行完毕时 栈上的指针var e 消失,他指向的对象 O1的计数减1变为了1;这样当函数执行完毕,O1、O2的这两个对象的计数都为1,根据计数的回收算法,就都留在内存里了不能被GC回收了,就造成了内存泄露。

上面说法不完全正确,实际上执行完fa后O2的计数是2,这个大家可以想一下原因。

其实fa里面的宿主对象只是真正对象一个副本,当执行

e.event 这句指令的时候 做了两件事,一个是副本的对象指向O2 这时O2的计数加1,真正的宿主对象又指向这个O2,这个O2的计数再加1 变为了2
所以 在fa的外面执行 e.event=null 的时候,这时O2计数减1变为了1, 这时候,栈上再没有指向O2的指针了,所以O2的计数再没有减少的机会了。这样O2就永远存在了,O2存在,那么O2的作用域链指向O1的指针就永远存在了,所以O1也就永远存了,这样O1、O2 就再没机会释放了,就造成了内存泄露。
 


 
 
 

js闭包和ie内存泄露原理的更多相关文章

  1. 也议 js闭包和ie内存泄露原理

    可以, 但小心使用. 闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档. 有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能 ...

  2. 前端知识体系:JavaScript基础-作用域和闭包-闭包的实现原理和作用以及堆栈溢出和内存泄漏原理和相应解决办法

    闭包的实现原理和作用 闭包: 有权访问另一个函数作用域中的变量的函数. 创建闭包的常见方式就是,在一个函数中创建另一个函数. 闭包的作用: 访问函数内部变量.保持函数在环境中一直存在,不会被垃圾回收机 ...

  3. 关于js闭包是否真的会造成内存泄漏(转载)

    闭包是一个非常强大的特性,但人们对其也有诸多无解.一种危言耸听的说法是闭包会造成内存泄露. 局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直 ...

  4. Javascript内存泄露

    在过去一些的时候,Web开发人员并没有太多的去关注内存泄露问题.那时的页面间联系大都比较简单,并主要使用不同的连接地址在同一个站点中导航,这样的设计方式是非常有利于浏览器释放资源的.即使Web页面运行 ...

  5. 【javascript】内存泄露及其解决办法

    1.内存泄露:一般由于开发者使用不当导致不用的内存没有被操作系统或者空闲内存池回收释放. 2.造成内存泄露的常见原因: 1) 意外的全局变量引起的内存泄露 2)闭包引起的内存泄露 闭包可以维持函数内局 ...

  6. 转自知乎大神----JS 闭包是什么

    大名鼎鼎的闭包!这一题终于来了,面试必问. 请用自己的话简述 什么是「闭包」. 「闭包」的作用是什么. --------------------------------------- 首先来简述什么是 ...

  7. 如何快速排查解决Android中的内存泄露问题

    概述 内存泄露是Android开发中比较常见的问题,一旦发生会导致大量内存空间得不到释放,可用内存急剧减少,导致运行卡顿,部分功能不可用甚至引发应用crash.对于复杂度比较高.多人协同开发的项目来讲 ...

  8. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  9. JS中由闭包引发内存泄露的深思

    目录 一个存在内存泄露的闭包实例 什么是内存泄露 JS的垃圾回收机制 什么是闭包 什么原因导致了内存泄露 参考 1.一个存在内存泄露的闭包实例 var theThing = null; var rep ...

随机推荐

  1. 1220 - Mysterious Bacteria--LightOj1220 (gcd)

    http://lightoj.com/volume_showproblem.php?problem=1220 题目大意: 给你一个x,求出满足 x=b^p, p最大是几. 分析:x=p1^a1*p2^ ...

  2. 【C#学习笔记】调用C++生成的DLL

    首先用vs2010建立win32项目,选择dll和空项目. 头文件add.h extern "C" __declspec(dllexport) int add(int a,int ...

  3. 达到HTTP合约Get、Post和文件上传功能——采用WinHttp介面

    于<采用WinHttp实现HTTP协议Get.Post和文件上传功能>一文中,我已经比較具体地解说了怎样使用WinHttp接口实现各种协议. 在近期的代码梳理中,我认为Post和文件上传模 ...

  4. python 脚本自动登陆校园网

    学校的校园网每次重开电脑时都要重新打开浏览器进行网页登录,繁琐的操作比较麻烦,于是便写了个python的脚本进行自动登录,下面说下具体的操作过程: 1. 方法说明 博主采用的python的 reque ...

  5. For each loop in Native C++

    今天发现 for each 语法居然可以直接编译通过,之前还以为只有开了/clr才可以支持.查了一下资料发现ms从vs2005就已经支持了.虽然不符合标准不过用着确实方便啊,必须记录一下. 具体看这里 ...

  6. 一个具有缓存数据功能的HttpWebRequest工具类

    背景:一个公共站点中的数据,供其它子站点共享,为了提高性能,简单实现了Http 1.1的缓存功能 特点:可以缓存Html数据到内存中;缓存具有过期时间;缓存过期后,通过再确认的方式来决定是否更新缓存; ...

  7. git 保存用户名密码

    打开本地的.git/config 加入 [credential] helper = store 保存,第一次需要输入用户名密码,输入一次密码后第二次就会记住密码了不会再提示输入用户名及密码

  8. GeoAnalytics Server学习笔记

    GA的输入数据源 输入源 存储形式 Spatiotemporal 时空型ArcGIS DataStore 物联网数据 (通过GeoEvent Server输出) 大数据共享目录BigDataShare ...

  9. luogu P4115 Qtree4

    题目链接 luogu P4115 Qtree4 题解 动态点分治,和上一题一样.同样三个堆.就是带权,用边权替换深度就好 为什么要单独写这个题解呢,因为我卡常卡了一天....据说树剖比rmq快? 在第 ...

  10. GET 对比 POST

    HTTP 方法:GET 对比 POST HTTP 消息 标签列表(字母排序) 两种最常用的 HTTP 方法是:GET 和 POST. 什么是 HTTP? 超文本传输协议(HTTP)的设计目的是保证客户 ...