js闭包和ie内存泄露原理
也议 js闭包和ie内存泄露原理
闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档.
有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能会产生循环引用, 进一步导致内存泄漏. 比如下面的代码:
function foo(element, a, b) {
element.onclick = function() { /* uses a and b */ };
}
这里, 即使没有使用 element
, 闭包也保留了 element
, a
和 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内存泄露原理的更多相关文章
- 也议 js闭包和ie内存泄露原理
可以, 但小心使用. 闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档. 有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能 ...
- JavaScript之详述闭包导致的内存泄露
一.内存泄露 1. 定义:一块被分配的内存既不能使用,也不能回收.从而影响性能,甚至导致程序崩溃. 2. 起因:JavaScript的垃圾自动回收机制会按一定的策略找出那些不再继续使用的变量,释放其占 ...
- 前端知识体系:JavaScript基础-作用域和闭包-闭包的实现原理和作用以及堆栈溢出和内存泄漏原理和相应解决办法
闭包的实现原理和作用 闭包: 有权访问另一个函数作用域中的变量的函数. 创建闭包的常见方式就是,在一个函数中创建另一个函数. 闭包的作用: 访问函数内部变量.保持函数在环境中一直存在,不会被垃圾回收机 ...
- 关于js闭包是否真的会造成内存泄漏(转载)
闭包是一个非常强大的特性,但人们对其也有诸多无解.一种危言耸听的说法是闭包会造成内存泄露. 局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直 ...
- Javascript内存泄露
在过去一些的时候,Web开发人员并没有太多的去关注内存泄露问题.那时的页面间联系大都比较简单,并主要使用不同的连接地址在同一个站点中导航,这样的设计方式是非常有利于浏览器释放资源的.即使Web页面运行 ...
- 【javascript】内存泄露及其解决办法
1.内存泄露:一般由于开发者使用不当导致不用的内存没有被操作系统或者空闲内存池回收释放. 2.造成内存泄露的常见原因: 1) 意外的全局变量引起的内存泄露 2)闭包引起的内存泄露 闭包可以维持函数内局 ...
- 转自知乎大神----JS 闭包是什么
大名鼎鼎的闭包!这一题终于来了,面试必问. 请用自己的话简述 什么是「闭包」. 「闭包」的作用是什么. --------------------------------------- 首先来简述什么是 ...
- 如何快速排查解决Android中的内存泄露问题
概述 内存泄露是Android开发中比较常见的问题,一旦发生会导致大量内存空间得不到释放,可用内存急剧减少,导致运行卡顿,部分功能不可用甚至引发应用crash.对于复杂度比较高.多人协同开发的项目来讲 ...
- 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理
[微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...
随机推荐
- Android 自己的自动化测试(5)<robotium>
大约Android自己的自动化测试UI测试,前出台Android 自己主动化測试(4)<uiautomator>, 在android原生的单元測试框架上,利用uiautomator.jar ...
- EF中的贪婪加载和延迟加载(懒加载)
在上一章中,我们使用了Linq对Entity Framework进行了一个查询,但是通过学习我们却发现了懒加载给我来的性能上的开销是很到的,尤其是在循环中,如果数据量不是很多的情况下还可以接受,如果数 ...
- wcf系列5天速成——第一天 binding的使用(1)
原文:wcf系列5天速成--第一天 binding的使用(1) 作为WCF速成系列,只介绍些项目开发中常用到的实战知识. 学习wcf,还是对其中的几个术语要了解一下.wcf中有一个ABC的概念,就是 ...
- 使用Json让Java和C#沟通的方法
原文:使用Json让Java和C#沟通的方法 最近很忙啊,新项目下来了,都没时间写博客了.频率降低点,但不能不总结跟大家分享啊. 我们在项目里经常要涉及到各模块间的通信,这其中又不可避免要碰到各类语言 ...
- JqueryAjax异步加载在ASP.NET
前台代码 <script src="Scripts/jquery-1.4.1.min.js" type="text/javascript">< ...
- 平安某金所奇葩的面经-关于幂等和ROA设计的反思
在公司一直在做跟支付有关的项目,某日接到平安某金所一男子电话,应该是之前某猎头投的,我正好在吃早饭(也不能怪他们上班早,我们公司弹性工作制,我一般上班比较晚). 因为饭馆信号不好,只能赶紧放下剩下的半 ...
- ECLIPSE实现了界面显示所有类
按下接口名称CTRL+T实现全面实施的接口类显示 版权声明:本文博客原创文章,博客,未经同意,不得转载.
- OC的构造方法与分类知识点总结
OC语言构造方法: 用来初始化对象的方法,对象方法,以减号开头 为了让对象创建出来,成员变量就会有一些固定的值 重写构造方法的注意点: 先调用父类的构造方法([super init]) 再进行子类内部 ...
- sql分页查询公式
分页查询公式: select top PageRow(每页显示的数据行数) from 表名 where 主键 not in(select top PageRow*(当前页数-1) 主键 from ...
- JS获取渲染后的样式
一般我们利用element.style.属性来获取CSS的样式,而此方法只能获取标签内的样式,无法获取头部或引入的样式,因此,而我们又需要获取其样式,则我们可以使用:(其中element为标签,pro ...