JavaScript 闭包总结 (深入理解)
什么是闭包
简单的说闭包就是函数里面的函数,《JavaScript高级程序设计》里是这样定义的
闭包是指有权访问另一个函数作用域中的变量的函数。
先看一道面试时经常被考的题目
- 代码1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>lzhTest</title>
</head>
<body>
<ul>
<li>0</li>
<li>1</li>
</ul>
<script>
var lis = document.getElementsByTagName("li");
for(var i = 0; i < lis.length; i++){
lis[i].onclick = function(event){
alert(i);
}
}
</script>
</body>
</html>
分别点击 li,alert什么?答案均是 2. 为什么呢?我们接着往下看
作用域链和活动对象
函数被调用时会创建一个执行环境和作用域链 (scope chain),作用域链中每个元素都指向一个活动对象或变量对象 (执行环境中定义的所有变量和函数都保存在这个对象中,包括 this、arguments),函数执行完毕,作用域链被销毁,如果这时相应的变量对象没有被引用,则变量对象占用的空间会被释放。
比如上面题目中的作用域链是这样的:
匿名函数1
和 匿名函数2
是两个事件处理函数,从图中可以看出,在作用域链的最前端(即下标为0)对应的活动对象中,是不存在 i
的,i
在全局变量对象中,点击的时候,需要往作用域的上层查找 i
,于是就找到了全局变量对象中的 i
,因为点击的时候,i
早已增加成为 2,所以 alert 的 i
均为 2。
怎样做到每次 alert 的是下标呢?
直接看代码:
代码2
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>lzhTest</title> </head> <body> <ul> <li>0</li> <li>1</li> </ul> <script> var lis = document.getElementsByTagName("li"); var helper = function(i){ return function(event) { alert(i); } } for(var i = 0; i < lis.length; i++){ lis[i].onclick = helper(i); } // 参考自:《JavaScript语言精粹》 </script> </body> </html>
对应的作用域链及活动对象:
- 代码2中,全局变量对象中 i 的变化用 0->2 表示 (代码1也如此)
- 第一次调用 helper 时,全局变量对象中 i 是0,所以此时 helper(1) 的活动对象中 i 是0,因为是以形参的形式从全局变量对象中传进来的。此后 helper(1) 中的 i 就不变了。
- 接着 helper(1) 中返回一个
匿名函数1
,(根据《JavaScript高级程序设计》介绍:函数在调用时生成作用域链和活动对象,但闭包被返回时,就会生成作用域链和活动对象(中文第3版 P180 line7),我的理解并不是这样的),返回的匿名函数1
引用着 helper1 中的i,helper(1) 执行完毕,之后 helper(1)的执行环境和作用域链销毁,但是 helper(1) 的活动对象还在,因为匿名函数1
的作用域链还在引用着它(按照我的理解应该是匿名函数1
还引用着它)。(执行环境和作用域链销毁这一过程在图中没有体现出来)。 - 如果用户 click lis[0],那么就会调用
匿名函数1
,(按照我的理解:此时匿名函数1的执行环境才会被压入环境栈中,同时生成匿名函数1
的作用域链和活动对象),alert(i)
时,因为匿名函数1
的活动对象中找不到i
所以往作用域链的父级找,找到了 helper(1) 活动对象中的 i,于是alert
了 0 - 在第二次调用 helper 时,生成的作用域链和活动对象是新的了,与 helper(1) 中的不同,同理,当用户 click lis[1] 时,
alert 1
,如果此时还不是很懂的话,可以回头再看看图,或者从 代码1 那里从新理解。 如果有认真看的话,请思考一下我的看法和《JavaScript高级程序设计》的看法,到底哪个是正确的,我还是坚持自己的看法,如果有错的话,还请指出。
由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存。过度使用闭包可能会导致内存占用过多。
来看一个闭包内引用着活动对象中的变量时,活动对象不被释放的例子,注意看图片右侧的 scope:
从图中可以看出,param1、param3 所在的活动对象不在作用域链中,应该是被通知准备回收了或者已经回收了,而 param2、param4 所在的变量对象还在。
另外,如果闭包中有
eval
的话,由于不能判断eval
里是否有引用父级作用域链活动对象中的变量,那么该作用域链中的所有活动对象都会被保留,所以在闭包中尽量不要使用eval
:但如果在里面用的是
new Function("这里引用闭包外的变量")
这种写法,如果没有其它引用,父级作用域链的活动对象是不会保留的,下面这种写法最终会报错Uncaught ReferenceError: param1 is not defined
:
内存泄露
如果闭包的作用域链中保存着一个HTML 元素,那么就意味着该元素将无法被销毁,代码如下,只要匿名函数存在,element 的引用数至少也是 1,因此它所占用的内存就永远不会被回收。(书中有讨论到这是IE9以前的问题,我怎么觉得这是个普遍的问题呢,难道 Chrome 或其它浏览器中 element 的引用数至少还能是0吗?求不吝赐教)。
function assignHandler(){
var element = document.getElementById("someElement");
element.onclick = function(){
alert(element.id);
};
}
- 所以《JavaScript高级程序设计》建议这么写:
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
闭包的作用
闭包无处不在,如果真的要归纳几个用处,如下:
模仿块级作用域
我们都知道,在 JavaScript 中,是不存在块级作用域的,也就是在 {} 里面声明的变量,在 {} 外面依然可以访问。但如果利用函数一旦执行完,其中执行环境和作用域链均销毁的特性,我们可以这么做:
(function(){
// 让一个括号包着一个函数,相当于得到这个函数的引用,
// 然后再在后面加个括号,执行这个函数,称这种函数为 立即执行函数
// 在这里面声明的变量,外部不可访问,除非 return 一个闭包或变量
// 如果此时外部是一个函数的话,那这个立即执行函数也是一个闭包
// 只是这个闭包并没有返回些什么
})();
模块化开发
有了上面提到的模仿块级作用域,就可以减少全局变量的使用,jQuery 就是这么做的:
(function(window, undefined){
// ...
// 这里实现 jQuery 的所有功能
// 势必会声明很多变量,如果暴露在全局作用域中会造成命名冲突
// 所以用一个立即执行函数包起来,但是为什么要传入 window 呢
// 传入 window 是为了让 window 成为当前作用域下的变量,这样可以减少访问成本,
// 另外便于压缩,比如将 window 压缩成 e,外部传进来的依然是 window
// 传入 undefined 的原因:
// 在低版本的 IE 中,undefined 是可写的,有可能 undefined 就不是 undefined 了
window.jQuery = window.$ = jQuery;
// 这里的 jQuery 就是一个函数,平时我们用的时候是 $(参数、参数),所以,他也是个闭包嘛
// 上面是模块中的一种,而旦还有更高级的,jQuery 兼容 AMD 规范
if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
define( "jquery", [], function () {
// AMD 这里不详细介绍了,这里就是 define 一个模块
// 这里也是一个闭包
return jQuery; // 这里就是闭包中的闭包了
} );
}
}(window))
- 闭包无处不在
JavaScript 闭包总结 (深入理解)的更多相关文章
- JavaScript闭包的一些理解
原文:JavaScript闭包的一些理解 简单一点的说:闭包就是能够读取其他函数内部变量的函数.那如何实现读取其它函数内部变量呢,大家都知道在JavaScript中内部函数可以访问其父函数中的变量,那 ...
- 对JavaScript闭包和原型理解
最近在学js脚本的一些东西觉得里面有2个知识点比较难理解所以做了如下总结. 1.闭包 简单的理解:一个函数a ,内部有个函数b,那么这个函数b当被作为a函数的返回值得时候被外部的全局变量引用了,那么这 ...
- javascript 闭包最简单理解
首先说3点与闭包有关系的东西. 一.变量的作用域 变量的作用域不难理解. 1.函数内部可以访问函数外部的变量,而函数外部不能访问函数内部的变量. 2.如果在函数内定义变量的时候,不加var,那么是全局 ...
- 我也谈javascript闭包的原理理解
参考原文:http://www.oschina.net/question/28_41112 前言:还是一篇入门文章.Javascript中有几个非常重要的语言特性——对象.原型继承.闭包.其中闭包 对 ...
- JavaScript 闭包(个人理解)
当function里嵌套function时,内部的function可以访问外部function里的变量.但这不是闭包 function foo(x) { var tmp = 3; function b ...
- JavaScript闭包的深入理解
闭包算是javascript中一个比较难理解的概念,想要深入理解闭包的原理,首先需要搞清楚其他几个概念: 一.栈内存和堆内存 学过C/C++的同学可能知道,计算机系统将内存分为栈和堆两部分(大学的基础 ...
- JavaScript闭包函数的理解
闭包就是一个函数能够访问其函数外部作用域中的变量,即在外面可以调用函数中的函数的变量,其实他就是将函数内外部连接起来的桥梁 闭包三大特点: 1. 函数嵌套函数 2. 内部函数可以访问外部函数的变量 3 ...
- 关于JavaScript闭包的粗浅理解
在JavaScript中,使用var创建变量,会创建全局变量或局部变量. 只有在非函数内创建的变量,才是全局变量,该变量可以在任何地方被读取. 而在函数内创建变量时,只有在函数内部才可读取.在函数外部 ...
- JavaScript 闭包的详细分享(三种创建方式)(附小实例)
JavaScript闭包的详细理解 一.原理:闭包函数--指有权访问私有函数里面的变量和对象还有方法等:通俗的讲就是突破私有函数的作用域,让函数外面能够使用函数里面的变量及方法. 1.第一种创建方式 ...
- JavaScript闭包理解【关键字:普通函数、闭包、解决获取元素标签索引】
以前总觉得闭包很抽象,很难理解,所以百度一下"闭包"概览,百度的解释是:“闭包是指可以包含自由(未绑定到特定对象)变量的代码块:这些变量不是在这个代码块内或者任何全局上下文中定义的 ...
随机推荐
- 学习c++语言应该牢记的50条准则,同样学习其他语言也一样
1.把C++当成一门新的语言学习(和C没啥关系!真的.): 2.看<Thinking In C++>,不要看<C++变成死相>: 3.看<The C++ Programm ...
- STM32_IAP详解(有代码,有上位机)
Iap,全名为in applacation programming,即在应用编程,与之相对应的叫做isp,in system programming,在系统编程,两者的不同是isp需要依靠烧写器在单片 ...
- MIPI-1
未来的产品都将朝着移动的方向发展,例如智能手机.数码相机.摄像机.平板电脑.媒体播放器.游戏机等,这些产品需要能执行多任务,包括处理多个不同的传感器如麦克风.图像传感器.磁罗盘.三轴加速度计和精细的触 ...
- mysql 安装错误 解决方法
错误及警告信息:TIMESTAMP with implicit DEFAULT value is deprecated. Please use --explicit_defaults_for_time ...
- 全方位分析Objcetive-C Runtime 分类: ios技术 2015-03-11 22:29 77人阅读 评论(0) 收藏
本文详细整理了 Cocoa 的 Runtime 系统的知识,它使得 Objective-C 如虎添翼,具备了灵活的动态特性,使这门古老的语言焕发生机.主要内容如下: 引言 简介 与Runtime交互 ...
- js实现的文章输入检查与测速。
在群里聊天,一个群友求助.说要实现 文章对比输入,出错了标红,正确的标绿. 同时还需要统计正确率. 我一开始以为很容易,结果搞了半天.最后折腾出来了. 这里的思路如下:利用js的数组.将文章和输入的内 ...
- js动画学习笔记
<html> <head> <meta charest="utf-8"> <title>test</title> < ...
- java 开发之linux 下 tomcat
tomcat作为java开发的服务器,可以部署在各种系统中. 官方下载地址:http://tomcat.apache.org/download-80.cgi 1. 装tomcat 之前,先要装jdk ...
- swift button一些简单设置
1,按钮的创建(1)按钮有下面四种类型: UIButtonType.ContactAdd:前面带“+”图标按钮,默认文字颜色为蓝色,有触摸时的高亮效果 UIButtonType.DetailDiscl ...
- 按ctrl + c 播放下一曲音乐
./a.out . #include<stdio.h>#include<stdlib.h>#include<unistd.h>#include<dirent. ...