闭包

http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

JavaScript中必须提到的功能最强大的抽象概念之一:闭包。它究竟是做什么的呢?

 function makeAdder(a) {
return function(b) {
return a + b;
}
}
var x = makeAdder(5);
var y = makeAdder(20);
x(6); //
y(7); //

makeAdder这个名字本身应该能说明函数是用来做什么的:他创建了一个新的adder函数,这个函数自身带有一个参数,它被调用的时候这个参数会被加载在外层函数传进来的参数上。

这里发生的事情和前面介绍过的内嵌函数十分相似:一个函数被定义在了另外一个函数的内部,内部函数可以访问外部函数的变量。唯一的不同是,外部函数被返回了,那么常识告诉我们局部变量“应该”不再存在。但是它们却仍然存在——否则 adder 函数将不能工作。也就是说,这里存在makeAdder 的局部变量的两个不同的“副本”——一个是 a 等于5,另一个是 a 等于20。运行结果就是一个是11一个是27。

下面来说说到底发生了什么。当JavaScript执行一个函数时,都会创建一个作用域对象(scope object),用来保存在这个函数中创建的局部变量。他和被传入函数的变量一起被初始化。这与那些保存的所有全部变量和函数的全局变量(global object)类似,但仍有很重要的区别,第一,每次函数被执行的时候,就会创建一个新的,特定的作用域对象;第二,与全局对象(在浏览器里面是当做window对象来访问的)不同的是,你不能从JavaScript代码中直接访问作用与对象,也没有可以遍历当前作用与对象里面属性的方法。

所以当调用 makeAdder 时,解释器创建了一个作用域对象,它带有一个属性:a,这个属性被当作参数传入 makeAdder 函数。然后 makeAdder 返回一个新创建的函数。通常 JavaScript 的垃圾回收器会在这时回收 makeAdder 创建的作用域对象,但是返回的函数却保留一个指向那个作用域对象的引用。结果是这个作用域对象不会被垃圾回收器回收,直到指向 makeAdder 返回的那个函数对象的引用计数为零。

作用域对象组成了一个名为作用域链(scope chain)的链。它类似于原形(prototype)链一样,被 JavaScript 的对象系统使用。

一个闭包就是一个函数和被创建的函数中的作用域对象的组合。

闭包允许你保存状态——所以它们通常可以代替对象来使用。

内存泄漏

使用闭包的一个坏处是,在 IE 浏览器中它会很容易导致内存泄露。JavaScript是一种具有垃圾回收机制的语言——对象在创建时分配内存,然后当值想这个对象的引用计数为零时,浏览器会回收内存。宿主环境提供的对象都是按照这种方法被处理的。

浏览器主机需要处理大量的对象来描绘一个正在被展现的HTML页面——DOM对象。浏览器负责管理他们的内存分配和回收。

IE浏览器有自己一套垃圾回收机制,这套机制与JavaScript提供的垃圾回收机制交互时,可能发生内存泄漏。

在IE中,每当在一个JavaScript对象和一个本地对象之间形成循环引用时就会发生内存泄漏。如下所示:

 function leakMemory() {
var el = document.getElementById('el');
var o = { 'el': el };
el.o = o;
}

这段代码的循环引用会导致内存泄露:IE 不会释放被 el 和 o 使用的内存,直到浏览器被彻底关闭并重启后。

这个例子往往无法引起人们的重视:一般只会在长时间运行的应用程序中,或者因为巨大的数据量和循环中导致内存泄露发生时,内存泄露才会引起注意。

不过一般也很少发生如此明显的内存泄露现象——通常泄露的数据结构有多层的引用(references),往往掩盖了循环引用的情况。

闭包很容易发生无意识的内存泄露。如下所示:

 function addHandler() {
var el = document.getElementById('el');
el.onclick = function() {
el.style.backgroundColor = 'red';
}
}

这段代码创建了一个元素,当它被点击的时候变红,但同时它也会发生内存泄露。为什么?因为对el 的引用不小心被放在一个匿名内部函数中。这就在 JavaScript 对象(这个内部函数)和本地对象之间(el)创建了一个循环引用。

这个问题有很多种解决方法,最简单的一种是不要使用 el 变量:

 function addHandler(){
document.getElementById('el').onclick = function(){
this.style.backgroundColor = 'red';
};
}

有趣的是,有一种窍门解决因闭包而引入的循环引用,是添加另外一个闭包:

 function addHandler() {
var clickHandler = function() {
this.style.backgroundColor = 'red';
};
(function() {
var el = document.getElementById('el');
el.onclick = clickHandler;
})();
}

内部函数被直接执行,并在 clickHandler 创建的闭包中隐藏了它的内容。

另外一种避免闭包的好方法是在 window.onunload 事件发生期间破坏循环引用。很多事件库都能完成这项工作。注意这样做将使 Firefox 中的 bfcache 无法工作。所以除非有其他必要的原因,最好不要在 Firefox 中注册一个unload 的监听器。

JavaScript的闭包和内存泄漏问题的更多相关文章

  1. JavaScript闭包(内存泄漏、溢出以及内存回收),超直白解析

    1 引言 变量作用域 首先我们先铺垫一个知识点--变量作用域: 变量根据作用域的不同分为两种:全局变量和局部变量. 函数内部可以使用全局变量. 函数外部不可以使用局部变量. 当函数执行完毕,本作用域内 ...

  2. 转《js闭包与内存泄漏》

    首先,能导致内存泄漏的一定是引用类型的变量,比如函数和其他自定义对象.而值类型的变量是不存在内存泄漏的,比如字符串.数字.布尔值等.因为值类型是靠复制来传递的,而引用类型是靠类似c语言中的指针来传递的 ...

  3. js闭包避免内存泄漏 减少内存使用 避免对象无法回收注意事项

    闭包 如果闭包的作用域中保存着一个 HTML 元素,则该元素无法被销毁.(下面代码来自高程) 闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量. function a ...

  4. JavaScript内存泄漏知多少?

    垃圾回收解放了我们,它让我们可将精力集中在应用程序逻辑(而不是内存管理)上.但是,垃圾收集并不神奇.了解它的工作原理,以及如何使它保留本应在很久以前释放的内存,就可以实现更快更可靠的应用程序.在本文中 ...

  5. [转]Google开源Leak Finder—用于检测内存泄漏的JavaScript工具-----可惜,暂时打不开google的网站,下载不了

    近日,Google开源了Leak Finder,这款工具可以查看JavaScript应用的堆,进而发现内存泄漏. 作为一门垃圾收集语言,JavaScript并不会出现常见的内存泄露情况,特别是像C++ ...

  6. JavaScript之闭包与高阶函数(一)

    JavaScript虽是一门面向对象的编程语言,但同时也有许多函数式编程的特性,如Lambda表达式,闭包,高阶函数等. 函数式编程是种编程范式,它将电脑运算视为函数的计算.函数编程语言最重要的基础是 ...

  7. 浅析c#内存泄漏

    一直以来都对内存泄露和内存溢出理解的不是很深刻.在网上看到了几篇文章,于是整理了一下自己对内存泄露和内存溢出的理解. 一.概念 内存溢出:指程序在运行的过程中,程序对内存的需求超过了超过了计算机分配给 ...

  8. 高效使用 JavaScript 闭包,避免 Node.js 应用程序中的内存泄漏

    在 Node.js 中,广泛采用不同形式的闭包来支持 Node 的异步和事件驱动编程模型.通过很好地理解闭包,您可以确保所开发应用程序的功能正确性.稳定性和可伸缩性. 闭包是一种将数据与处理数据的代码 ...

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

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

随机推荐

  1. [Android]listview recycleview的复用问题

    最近解决了几个bug,是关于listview和recycle view中的复用问题的:   为了提高性能,我们使用了viewHolder来减少view的生成,从而提高滑动的性能:   要注意一个很隐蔽 ...

  2. django 操作 下载 excel xls xlsx csv

    网站开发离不开数据的导入导出,本文将介绍一下django如何操作excel 先安装 django-excel pip install django-excel 配置一下url url(r'^downl ...

  3. mysql执行计划

         烂sql不仅直接影响sql的响应时间,更影响db的性能,导致其它正常的sql响应时间变长.如何写好sql,学会看执行计划至关重要.下面我简单讲讲mysql的执行计划,只列出了一些常见的情况, ...

  4. Sql Server之旅——第八站 复合索引和include索引到底有多大区别?

    周末终于搬进出租房了,装了宽带....才发现没网的日子...那是一个怎样的与世隔绝呀...再也受不了那样的日子了....好了,既然网 安上去了,还得继续我的这个系列. 索引和锁,这两个主题对我们开发工 ...

  5. Autofac全面解析系列(版本:3.5) – [使用篇(推荐篇):1.类型注册]

    前言 Autofac Autofac是一套高效的依赖注入框架. Autofac官方网站:http://autofac.org/ Autofac在Github上的开源项目:https://github. ...

  6. 烂泥:openvpn双网卡客户端与内网机器通信

    本文由ilanniweb提供友情赞助,首发于烂泥行天下 想要获得更多的文章,可以关注我的微信ilanniweb. 前段时间写了一篇有关openvpn搭建与内网机器通信的文章,那篇文章是基于服务器单网卡 ...

  7. android setLayoutParams 问题,出错

    LinearLayout layt = (LinearLayout) rootView.findViewById(R.id.llt_2); FrameLayout.LayoutParams layou ...

  8. java怎么建立JAVA工程项目?

    File->New->Java Project;src->New->Class; 出现packet,运行出错的问题 然后如果不要包packet 的话,不要在此处填写包的名称就行 ...

  9. Office 2010 KMS激活原理和案例分享

    Office 2010 KMS激活原理和案例分享     为了减低部署盗版(可能包含恶意软件.病毒和其他安全风险)的可能性,Office 2010面向企业客户推出了新的批量激活方式:KMS和MAK.这 ...

  10. Java内存模型与垃圾回收

    1.Java内存模型 Java虚拟机在执行程序时把它管理的内存分为若干数据区域,这些数据区域分布情况如下图所示: 程序计数器:一块较小内存区域,指向当前所执行的字节码.如果线程正在执行一个Java方法 ...