初学者经常碰到的, 即获取 html 元素集合,循环给元素添加事件。在事件响应函数中 (event handler) 获取对应的索引。但每次获取的都是最后一次循环的索引。原因是初学者并未理解 JavaScript 的闭包特性。 有个网友问了个问题,如下的 html,为什么点击所有的段落 p 输出都是 5,而不是 alert 出对应的 0,1,2,3,4。 1. <!DOCTYPE HTML> 2. <html> 3. <head> 4. <meta charset="utf-8"/> 5. <title> 闭包演示 </title> 6. <style type="text/CSS"> 7. p {background:gold;} 8. </style> 9. <script type="text/javascript"> 10.function init() { 11. var pAry = document.getElementsByTagName("p"); 12. for(var i=0; i<pAry.length; i++) { 13. pAry[i].onclick = function() { 14. alert(i); 15. } 16. } 17.} 18.</script> 19.</head> 20.<body onload="init();"> 21.<p> 产品 0</p> 22.<p> 产品 1</p> 23.<p> 产品 2</p> 24.<p> 产品 3</p> 25.<p> 产品 4</p> 26.</body> 27.</html> 以上场景是初学者经常碰到的。即获取 HTML 元素集合,循环给元素添加事件。在事件响应函数中 (event handler) 获取对应的索引。但每次获取的都是最后一次循环的索引。 原因是初学者并未理解 JavaScript 的闭包特性。通过 element.onclick=function(){alert(i);} 方式给元 素添加点击事件。响应函数 function(){alert(i);} 中的 i 并非每次循环时对应的 i(如 0,1,2,3,4) 而是循环后最后 i 的值 5。 或者说循环时响应函数内并未能保存对应的值 i,而是最后一次 i++ 的值 5。 了解了原因,下面就由几种方式可与解决: 1、将变量 i 保存给在每个段落对象 (p) 上 1. function init1() { 2. var pAry = document.getElementsByTagName("p"); 3. for(var i=0; i<pAry.length; i++) { 4. pAry[i].i = i; 5. pAry[i].onclick = function() { 6. alert(this.i); 7. } 8. } 9. } 2、将变量 i 保存在匿名函数自身 1. function init2() { 2. var pAry = document.getElementsByTagName("p"); 3. for(var i=0; i<pAry.length; i++) { 4. (pAry[i].onclick = function() { 5. alert(arguments.callee.i); 6. }).i = i; 7. } 8. } 3、加一层闭包,i 以函数参数形式传递给内层函数 1. function init3() { 2. var pAry = document.getElementsByTagName("p"); 3. for(var i=0; i<pAry.length; i++) { 4. (function(arg){ 5. pAry[i].onclick = function() { 6. alert(arg); 7. }; 8. })(i);// 调用时参数 9. } 10.} 4、加一层闭包,i 以局部变量形式传递给内层函数 1. function init4() { 2. var pAry = document.getElementsByTagName("p"); 3. for(var i=0; i<pAry.length; i++) { 4. (function () { 5. var temp = i;// 调用时局部变量 6. pAry[i].onclick = function() { 7. alert(temp); 8. } 9. })(); 10. } 11.} 5、加一层闭包,返回一个函数作为响应事件 (注意与 3 的细微区别) 1. function init5() { 2. var pAry = document.getElementsByTagName("p"); 3. for(var i=0; i<pAry.length; i++) { 4. pAry[i].onclick = function(arg) { 5. return function() {// 返回一个函数 6. alert(arg); 7. } 8. }(i); 9. } 10.} 6、用 Function 实现,实际上每产生一个函数实例就会产生一个闭包 1. function init6() { 2. var pAry = document.getElementsByTagName("p"); 3. for(var i=0; i<pAry.length; i++) { 4. pAry[i].onclick = new Function("alert(" + i + ");");//new 一次就产生一个函数实例 5. } 6. } 7、用 Function 实现,注意与 6 的区别 1. function init7() { 2. var pAry = document.getElementsByTagName("p"); 3. for(var i=0; i<pAry.length; i++) { 4. pAry[i].onclick = Function('alert('+i+')'); 5. } 6. } |
2009-07-24 17:30 司徒正美 cnblogs 我要评论 (1) 字号: T | T
本文将对 Javascript 闭包的特性进行分析,并举例进行说明。闭包,是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落。
AD:
Javascript 闭包的定义非常晦涩——闭包,是指语法域位于某个特定的区域,具有持续参照(读写)位于该区域内自身范围之外的执行域上的非持久型变量值能力的段落。这些外部执行域的非持久型变量神奇地保留它们在闭包最初定义(或创建)时的值(深连结)。
简单来说,Javascript 闭包就是在另一个作用域中保存了一份它从上一级函数或作用域取得的变量(键值对),而这些键值对是不会随上一级函数的执行完成而销毁。周爱民说得更清楚,闭包就是 "属性表",闭包就是一个数据块,闭包就是一个存放着 "Name=Value" 的对照表。就这么简单。但是,必须强调,闭包是运行期概念,一个函数实例。
Javascript 闭包的实现,通常是在函数内部再定义函数,让该内部函数使用上一级函数的变量或全局变量。
ECMAScript 认为使用全局变量是一个简单的 Javascript 闭包实例。
1. var sMessage = "Hello World";
2. function sayHelloWorld(){
3. alert(sMessage);
4. };
5. sayHelloWorld();
但它完成没有体现 Javascript 闭包的特性……
现在比较让人认同的 Javascript 闭包实现有如下三种
1. with(obj){
2. // 这里是对象闭包
3. }(function(){
4. // 函数闭包
5. })()try{
6. //...
7. } catch(e) {
8. //catch 闭包 但 IE 里不行
9. }
附上今天在无忧看到的问题:
要求:
让这三个节点的 Onclick 事件都能正确的弹出相应的参数。
1. <ul>
2. <li id="a1">aa</li>
3. <li id="a2">aa</li>
4. <li id="a3">aa</li>
5. </ul>
6. <script type="text/javascript">
7. <ul>
8. <li id="a1">aa</li>
9. <li id="a2">aa</li>
10. <li id="a3">aa</li>
11. </ul>
12. <script type="text/javascript">
13. for(var i=1; i < 4; i++){
14. var id = document.getElementById("a" + i);
15. id.onclick = function(){
16. alert(i);// 现在都是返回 4
17. }
18. }
19. </script>
客服果果的解答:
1. for(var i=1; i < 4; i++){
2. var id = document.getElementById("a" + i);
3. /*
4. 这里生成了一个匿名函数并赋值给对象 id_i;
5. */
6. id.onclick = function(){
7. /*
8. 这个 i 来源于局部变量,无法以 window.i 或者 obj.i 的形式在后期引用,
9. 只好以指针或者变量地址方式保存在这个匿名函数中,
10. 这就是传说的闭包,所以所有这个过程中生成的事件句柄都使用引用
11. 的方式来持久这个变量,也就是这些匿名函数共用一个变量 i;
12. */
13. alert(i);
14. };
15. };
局部变全局
1. for(var i=1; i < 4; i++){
2. var id = document.getElementById("a" + i);
3. id.i=i;// 这个 i 有了根
4. id.onclick=function(){
5. alert(this.i)
6. };
7. };1.for(var i=1; i < 4; i++){
8. var id = document.getElementById("a" + i);
9. window[id.id]=i;// 这个 i 有了根
10. id.onclick=function(){
11. alert(window[this.id]);
12. };
13. }
产生一对一的更多 Javascript 闭包
1. for(var i=1; i < 4; i++){
2. var id = document.getElementById("a" + i);
3. id.onclick = new function(){
4. var i2=i;// 这个 i 是闭包的闭包
5. return function(){
6. alert(i2);
7. }
8. };
9. }
javascript 深入理解 js 闭包发布:dxy 字体:[增加 减小] 类型:转载
闭包(closure)是 Javascript 语言的一个难点,也是它的特色,很多高级应用都要依靠闭包实现。
一、变量的作用域
要理解闭包,首先必须理解 Javascript 特殊的变量作用域。
变量的作用域无非就是两种:全局变量和局部变量。
Javascript 语言的特殊之处,就在于函数内部可以直接读取全局变量。
Js 代码
var n=999;
function f1(){
alert(n);
}
f1(); // 999
另一方面,在函数外部自然无法读取函数内的局部变量。
Js 代码
function f1(){
var n=999;
}
alert(n); // error
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明了一个全局变量!
Js 代码
function f1(){
n=999;
}
f1();
alert(n); // 999
--------------------------------------------------------------------------------------------------------
二、如何从外部读取局部变量?
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。
那就是在函数的内部,再定义一个函数。
Js 代码
function f1(){
n=999;
function f2(){
alert(n); // 999
}
}
在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。这就是 Javascript 语言特有的 "链式作用域" 结构(chain scope),
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!
Js 代码
function f1(){
n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
--------------------------------------------------------------------------------------------------------
三、闭包的概念
上一节代码中的 f2 函数,就是闭包。
各种专业文献上的 "闭包"(closure)定义非常抽象,很难看懂。我的理解是,闭包就是能够读取其他函数内部变量的函数。
由于在 Javascript 语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成 "定义在一个函数内部的函数"。
所以,在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。
--------------------------------------------------------------------------------------------------------b
四、闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。
怎么来理解这句话呢?请看下面的代码。
Js 代码
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),这时一定要小心,不要随便
改变父函数内部变量的值。
--------------------------------------------------------------------------------------------------------
六、思考题
如果你能理解下面代码的运行结果,应该就算理解闭包的运行机制了。
Js 代码
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()()); //The Window
--------------------------------------------------------------------------------------------------------
JavaScript 闭包例子
function outerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
}
innerFun()
上面的代码是错误的. innerFun() 的作用域在 outerFun() 内部, 所在 outerFun() 外部调用它是错误的.
改成如下, 也就是闭包:
Js 代码
function outerFun()
{
var a=0;
function innerFun()
{
a++;
alert(a);
}
return innerFun; // 注意这里
}
var obj=outerFun();
obj(); // 结果为 1
obj(); // 结果为 2
var obj2=outerFun();
obj2(); // 结果为 1
obj2(); // 结果为 2
什么是闭包:
当内部函数 在定义它的作用域 的外部 被引用时, 就创建了该内部函数的闭包 , 如果内部函数引用了位于外部函数的变量, 当外部函数调用完毕后, 这些变量在内存不会被 释放, 因为闭包需要它们.
--------------------------------------------------------------------------------------------------------
再来看一个例子
Js 代码
function outerFun()
{
var a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);
结果是 0,4 . 因为在函数内部使用了 var 关键字 维护 a 的作用域在 outFun() 内部.
再看下面的代码:
Js 代码
function outerFun()
{
// 没有 var
a =0;
alert(a);
}
var a=4;
outerFun();
alert(a);
结果为 0,0 真是奇怪, 为什么呢
作用域链是描述一种路径的术语, 沿着该路径可以确定变量的值 . 当执行 a=0 时, 因为没有使用 var 关键字, 因此赋值操作会沿着作用域链到 var a=4; 并改变其值.
--------------------------------------------------------------------------------------------------------------------------------------------------
如果你对 javascript 闭包还不是很理解,那么请看下面转载的文章:(转载: http://www.felixwoo.com/archives/247)
一、什么是闭包?
官方 " 的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
相信很少有人能直接看懂这句话,因为他描述的太学术。其实这句话通俗的来说就是:JavaScript 中所有的 function 都是一个闭包。不过一般来说,嵌套的 function 所产生的闭包更为强大,也是大部分时候我们所谓的 "闭包"。看下面这段代码:
function a() { var i = 0; function b() {alert(++i); } return b;}var c = a();c(); 这段代码有两个特点:
1、函数 b 嵌套在函数 a 内部;
2、函数 a 返回函数 b。
引用关系如图:
这样在执行完 var c=a() 后,变量 c 实际上是指向了函数 b,再执行 c() 后就会弹出一个窗口显示 i 的值 (第一次为 1)。这段代码其实就创建了一个闭包,为什么?因为函数 a 外的变量 c 引用了函数 a 内的函数 b,就是说:
当函数 a 的内部函数 b 被函数 a 外的一个变量引用的时候,就创建了一个闭包。
让我们说的更透彻一些。所谓 "闭包",就是在构造函数体内定义另外的函数作为目标对象的方法函数,而这个对象的方法函数反过来引用外层函数体中的临时变量。这使得只要目标 对象在生存期内始终能保持其方法,就能间接保持原构造函数体当时用到的临时变量值。尽管最开始的构造函数调用已经结束,临时变量的名称也都消失了,但在目 标对象的方法内却始终能引用到该变量的值,而且该值只能通这种方法来访问。即使再次调用相同的构造函数,但只会生成新对象和方法,新的临时变量只是对应新 的值,和上次那次调用的是各自独立的。
二、闭包有什么作用?
简而言之,闭包的作用就是在 a 执行完并返回后,闭包使得 Javascript 的垃圾回收机制 GC 不会收回 a 所占用的资源,因为 a 的内部函数 b 的执行需要依赖 a 中的变量。这是对闭包作用的非常直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包需要循序渐进的过程。
在上面的例子中,由于闭包的存在使得函数 a 返回后,a 中的 i 始终存在,这样每次执行 c(),i 都是自加 1 后 alert 出 i 的值。
那 么我们来想象另一种情况,如果 a 返回的不是函数 b,情况就完全不同了。因为 a 执行完后,b 没有被返回给 a 的外界,只是被 a 所引用,而此时 a 也只会被 b 引 用,因此函数 a 和 b 互相引用但又不被外界打扰 (被外界引用),函数 a 和 b 就会被 GC 回收。(关于 Javascript 的垃圾回收机制将在后面详细介绍)
三、闭包内的微观世界
如果要更加深入的了解闭包以及函数 a 和嵌套函数 b 的关系,我们需要引入另外几个概念:函数的执行环境 (excution context)、活动对象 (call object)、作用域 (scope)、作用域链 (scope chain)。以函数 a 从定义到执行的过程为例阐述这几个概念。
当定义函数 a 的时候,js 解释器会将函数 a 的作用域链 (scope chain) 设置为定义 a 时 a 所在的 "环境",如果 a 是一个全局函数,则 scope chain 中只有 window 对象。
当执行函数 a 的时候,a 会进入相应的执行环境 (excution context)。
在创建执行环境的过程中,首先会为 a 添加一个 scope 属性,即 a 的作用域,其值就为第 1 步中的 scope chain。即 a.scope=a 的作用域链。
然后执行环境会创建一个活动对象 (call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过 JavaScript 代码直接访问。创建完活动对象后,把活动对象添加到 a 的作用域链的最顶端。此时 a 的作用域链包含了两个对象:a 的活动对象和 window 对象。
下一步是在活动对象上添加一个 arguments 属性,它保存着调用函数 a 时所传递的参数。
最后把所有函数 a 的形参和内部的函数 b 的引用也添加到 a 的活动对象上。在这一步中,完成了函数 b 的的定义,因此如同第 3 步,函数 b 的作用域链被设置为 b 所被定义的环境,即 a 的作用域。
到此,整个函数 a 从定义到执行的步骤就完成了。此时 a 返回函数 b 的引用给 c,又函数 b 的作用域链包含了对函数 a 的活动对象的引用,也就是说 b 可以访问到 a 中定义的所有变量和函数。函数 b 被 c 引用,函数 b 又依赖函数 a,因此函数 a 在返回后不会被 GC 回收。
当函数 b 执行的时候亦会像以上步骤一样。因此,执行时 b 的作用域链包含了 3 个对象:b 的活动对象、a 的活动对象和 window 对象,如下图所示:
如图所示,当在函数 b 中访问一个变量的时候,搜索顺序是:
先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数 a 的活动对象,依次查找,直到找到为止。
如果函数 b 存在 prototype 原型对象,则在查找完自身的活动对象后先查找自身的原型对象,再继续查找。这就是 Javascript 中的变量查找机制。
如果整个作用域链上都无法找到,则返回 undefined。
小结,本段中提到了两个重要的词语:函数的定义与执行。文中提到函数的作用域是在定义函数时候就已经确定,而不是在执行的时候确定(参看步骤 1 和 3)。用一段代码来说明这个问题:
function f(x) {var g = function () {return x;} return g;}var h = f(1);alert(h()); 这段代码中变量 h 指向了 f 中的那个匿名函数 (由 g 返回)。
假设函数 h 的作用域是在执行 alert(h()) 确定的,那么此时 h 的作用域链是:h 的活动对象 ->alert 的活动对象 ->window 对象。
假设函数 h 的作用域是在定义时确定的,就是说 h 指向的那个匿名函数在定义的时候就已经确定了作用域。那么在执行的时候,h 的作用域链为:h 的活动对象 ->f 的活动对象 ->window 对象。
如果第一种假设成立,那输出值就是 undefined;如果第二种假设成立,输出值则为 1。
运行结果证明了第 2 个假设是正确的,说明函数的作用域确实是在定义这个函数的时候就已经确定了。
四、闭包的应用场景
保护函数内的变量安全。以最开始的例子为例,函数 a 中 i 只有函数 b 才能访问,而无法通过其他途径访问到,因此保护了 i 的安全性。
在内存中维持一个变量。依然如前例,由于闭包,函数 a 中 i 的一直存在于内存中,因此每次执行 c(),都会给 i 自加 1。
通过保护变量的安全实现 JS 私有属性和私有方法(不能被外部访问)
私有属性和方法在 Constructor 外是无法被访问的
function Constructor(...) {
var that = this;
var membername = value;
function membername(...) {...}
}
以上 3 点是闭包最基本的应用场景,很多经典案例都源于此。
五、Javascript 的垃圾回收机制
在 Javascript 中,如果一个对象不再被引用,那么这个对象就会被 GC 回收。如果两个对象互相引用,而不再被第 3 者所引用,那么这两个互相引用的对象也会被回收。因为函数 a 被 b 引用,b 又被 a 外的 c 引用,这就是为什么函数 a 执行后不会被回收的原因。
六、结语
理解 JavaScript 的闭包是迈向高级 JS 程序员的必经之路,理解了其解释和运行机制才能写出更为安全和优雅的代码。
详细出处参考:http://www.jb51.net/article/24101.htm###
来源: http://www.bubuko.com/infodetail-2446423.html