js闭包原理与例子[转]
闭包(closure)是Javascript语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
一、闭包原理:
一、变量的作用域
要理解闭包,首先必须理解Javascript特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量。
var n=999;
function f1(){
alert(n);
}f1(); // 999
另一方面,在函数外部自然无法读取函数内的局部变量。
function f1(){
var n=999;
}alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用var命令。如果不用的话,你实际上声明了一个全局变量!
function f1(){
n=999;
}f1();
alert(n); // 999
二、如何从外部读取局部变量?
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}}
在上面的代码中,函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!
function f1(){
var n=999;
function f2(){
alert(n);
}return f2;
}
var result=f1();
result(); // 999
三、闭包的概念
上一节代码中的f2函数,就是闭包。
各种专业文献上的"闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
四、闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
怎么来理解这句话呢?请看下面的代码。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
五、使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
六、思考题
如果你能理解下面两段代码的运行结果,应该就算理解闭包的运行机制了。
代码片段一。
var name = "The Window";
var object = {
name : "My Object",getNameFunc : function(){
return function(){
return this.name;
};}
};
alert(object.getNameFunc()());
代码片段二。
var name = "The Window";
var object = {
name : "My Object",getNameFunc : function(){
var that = this;
return function(){
return that.name;
};}
};
alert(object.getNameFunc()());
二、事件绑定和闭包
先说明下js的事件绑定或者叫事件监听。比如下面代码:
var i = 10; a.onclick = function(){
alert('this is ' + i) // this is 10
}
给
a对象绑定了click事件,click事件函数是弹出一个i的值,i在匿名函数内,没有这个变量,可是匿名函数外面的全局有这个i,所以最后会弹出10.这里面的过程是,堆内存定义一个匿名函数,然后栈里面有一个a对象的变量,这个变量对象又绑定到匿名函数。例子解释
这里模仿解释器的执行过程来解释。
题主的代码运行的时候,循环一个elems数组,每个数组绑定一个事件函数。内存大概如下
elem[0].addEventListener('click', function (e) {
...
alert('I am link #' + i); // 注意 这个 i 不是 循环变量。
}, 'false'); elem[1].addEventListener('click', function (e) {
...
alert('I am link #' + i);
}, 'false');
...
为什么不是
i不是循环变量?很简单,解释器读取循环的代码的时候,在栈内存生成了一些变量,比如elem[0]``elem[1]...,堆定义了一个匿名函数,这个匿名函数里面写的是什么,就是什么。比如这里匿名函数的函数内容是 alert('I am link #' + i); 每一个栈里的变量对象都绑定了堆里面那个匿名函数(事件函数).就是上面代码描述的样子。简图示意:
然后点击对象,触发点击事件的时候,就和前面
事件绑定与闭包的例子一样了。因为循环结束后,全局还存在一个i的变量,并且它的值是循环之后的值,也就是10.点击的时候就执行这个匿名函数。解决方法
因为
i相对匿名函数是外面的变量,就把循环绑定的时候,将i的值传入到匿名函数内,就可以了。因此需要在匿名函数(事件函数)外包裹一个匿名函数, 并立即执行。
var elems = document.getElementsByTagName('a'); for (var i = 0; i < elems.length; i++) { elems[i].addEventListener('click', (function (num) {
return function (e){
e.preventDefault();
alert('I am link #' + num);
}
})(i), 'false');
};
如果执行点击事件的时候,最终的事件函数外层因为立即执行的匿名函数,函数体内已经存在了
num变量,而这个num变量是每次循环的时候传入的i。另外一种解决方法没有用到闭包,而是给每个对象添加一个属性
var elems = document.getElementsByTagName('a');
for (var i = 0; i < elems.length; i++) {
elems[i].num = i;
elems[i].addEventListener('click', function (e) {
e.preventDefault();
alert('I am link #' + this.num);
}, 'false'); };
js闭包原理与例子[转]的更多相关文章
- js 闭包原理理解
问题?什么是js(JavaScript)的闭包原理,有什么作用? 一.定义 官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 很显然 ...
- js闭包原理
一.定义 官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. ****定义在函数中的函数,并且可在外部访问得到.(正常情况下我们是无法 ...
- js闭包的使用例子
网上关于闭包的介绍太多,这就导致了泛滥,对于新手来说,网上好多讲解就说了闭包是啥,还都是用下面这种例子: 我的天啊,我们都看了不知道多少遍了,看完有啥用?在什么场合下用啊? 于是我翻阅各种资料,自己总 ...
- Js(javaScript)的闭包原理
问题?什么是js(javaScript)的闭包原理,有什么作用? 一.定义 官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 小编 ...
- 也议 js闭包和ie内存泄露原理
可以, 但小心使用. 闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档. 有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 DOM 元素附加闭包时, 很可能 ...
- js闭包和ie内存泄露原理
也议 js闭包和ie内存泄露原理 可以, 但小心使用. 闭包也许是 JS 中最有用的特性了. 有一份比较好的介绍闭包原理的文档. 有一点需要牢记, 闭包保留了一个指向它封闭作用域的指针, 所以, 在给 ...
- JavaScript的闭包原理
什么是js(JavaScript)的闭包原理,有什么作用? 一.定义 官方解释:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. 个人的理解是 ...
- 大部分人都会做错的经典JS闭包面试题
由工作中演变而来的面试题 这是一个我工作当中的遇到的一个问题,似乎很有趣,就当做了一道题去面试,发现几乎没人能全部答对并说出原因,遂拿出来聊一聊吧. 先看题目代码: function fun(n,o) ...
- js闭包之初步理解( JavaScript closure)
闭包一直是js中一个比较难于理解的东西,而平时用途又非常多,因此不得不对闭包进行必要的理解,现在来说说我对js闭包的理解. 要理解闭包,肯定是要先了解js的一个重要特性, 回想一下,那就是函数作用域, ...
随机推荐
- Python库-BeautifulSoup
sp = BeautifulSoup.bs4.BeautifulSoup(html.text,"html.parser") 方法 1.sp.title 返回网页标题 2.sp.te ...
- 应用程序 system 函数
1.使用实例 system("ps"); //执行shell命令ps 2.使用注意事项 system相当于创建了一个子进程,在子进程中调用程序.所以system执行的程序会继承主进 ...
- web开发视频(一)之环境准备
硬件环境: Win7+64位操作系统 1.安装 jdk.tomcat.eclipse; 2.配置 jdk 环境变量.tomcat环境变量 (jdk配置成功的标示是在命令提示符中输入 javac 给出对 ...
- 【转】Jmeter之短板以及建议解决方案
随着JMeter的应用,发现JMeter的局限性越来越多,急需进一步扩展改进. 一.几百兆的sample 日志解析出现OutOfMemory 最近的几个项目都是Java sample 日志,应用都是高 ...
- HDU 4970 Killing Monsters(树状数组)
Killing Monsters Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Other ...
- PDA后台运行、安装程序
////启动最新版本安装(后台安装模式),结束更新程序 //string cabPath = @"\Application Data\QY.DDM.PDA.CAB&qu ...
- 在CentOS 7中使用VS Code编译调试C++项目
1. 安装VSCODE 见VSCode官方链接 https://code.visualstudio.com/docs/setup/linux#_rhel-fedora-and-centos-based ...
- Idea2016远程调试Java项目
一.在IDEA中配置远程tomcat 1.打开配置页面 选择"+" → "Tomcat Server" → "Remote" 填写名称和目标 ...
- 各自平台token获取解析及用户信息的获取
1.auth根据手机号码获取auth平台session_token记统一认证的user_id与pass_id [dwliuchao1@GD-QHD-CNG152TFKX-12.55 logs]$ cd ...
- python中django的第一课
今天找遍了,PYTHON项目中的文件,也没发现后台管理的这些页面去哪里修改 原来在pycharm中的外部库中,查找django,最终找到 $python\django\django\contrib\a ...
