循环一个节点列表(NodeList)或者数组,并且绑定事件处理函数引发对闭包的理解

循环一个节点列表(NodeList)或者数组,并且绑定事件处理函数引发对闭包的理解
    
    当然可以使用事件委托来实现,而且性能更好,但是这里纯粹为了理解这个问题。
    
    我在网上查阅了很多资料,有了一点理解,记录下来。如果有不对的地方,各位看客请指正。
    
    代码如下:
        
  

 HTML结构
<div id='one'></div> <div id='two'></div> <div id='three'></div>
JS处理 var odivs = document.getElementsByTagName('div'); for(var i=0; i<odivs.length; i++){ //遍历节点列表并且绑定事件 odivs[i].onclick = function(){ alert(i); } }

 


    
    当我们点击任何一个DIV时,弹出的都是3;
    
    这样会非常让人疑惑
    
    那么我们换给写法。
    
   

var one = document.getElementById('one');
    var two = document.getElementById('two');
    var three = document.getElementById('three');
    
    var i=0;
    
    one.onclick = function(){
        alert(i);
    }
    
    i++;
    
    two.onclick = function(){
        alert(i);    
    }
    
    i++;
    
    three.onclick = function(){
        alert(i);    
    }
    
    i++ // i=3  大于2 退出循环

 


    
    从这里知道,3这边值是i的最终的值。
    然后我们再看事件处理函数。
    
    function(){
        alert(i);    
    }
    
    从这里可以看出,这是一个函数声明,在面向对象中,这叫构造函数。
    
    这只是一个函数声明,并且绑定到点击事件上,并没有被执行。然后代码继续往下,执行i++;
    
    
    在网上看到这样一句话:
    
    之所以是这样的,看过《JavaScript: The Definitive Guide》就会明白:
所有的JavaScript函数都是闭包:它们都是对象,都关联到作用域链。每次调用JavaScript函数的时候,都会为之创建一个新的对象来保存局部变量,并把这个对象添加至作用域链中(简单理解:作用域链就是对象列表)。

    重点:"每次调用javascript函数时",局部变量会在函数被执行的时候创建,而不是声明的时候创建。
    
    
    上面的例子,我们很直观的看出。三个函数中的i共享全局变量i;事件处理函数并没有被执行,代码接着往下走,
    一直到脚本结束。 i的值变为3.
    
    这时候,点击其中任意一个div,然后显示的是全局的i,i=3,所以得出了3这个值。
    
    再往下看,可能有人会这么写,我就这么写过。
    
    for(var i=0; i<odivs.length; i++){
        
        odiv[i].onclick = function(){
            
            return function(o){
                
                alert(o);
                
            }(i);
            
        }
    }
    
    
    一看之下,好像没什么问题,变量i被一个内部函数保存了下来。函数也被执行了。
    
    但是,函数真的被执行了吗?
    
    分拆开来
    
    
odiv[i].onclick = function(){
                    
            return function(o){
                
                alert(o);
                
            }(i);
    }
    
    绑定了一个事件函数
    
    function(){
                    
            return function(o){
                
                alert(o);
                
            }(i);
    }
    
    可以看出,并没有被执行。代码还是继续往下走。
    
    并且代码也不会被执行。当时不知道怎么就写了这么个东西。
    
    
    js 中只有 function 才会划分作用域(和 python 有点像),if/else、for 循环都不会划分作用域,所以原来的方式循环引用的都是同一个变量 i。
    
    
    
    以上,我们可以得出一个结论:
    
    要想把i的值保存下来。必须在执行过程中把i放到一个函数中,并且函数需要立即执行,这样0,1,2的值才会被保留下来。
    那么修改代码
    for(var i=0; i<odivs.length; i++){
        
        odivs[i].onclick = show(i);
    
    }
    
    function show(i){
        
        alert(i);
    }
    
    那么,这样写行吗。显然是不行的。
    
    虽然在绑定事件的时候,调用了一个外部函数,并且将i传递进去保存了下来。
    
    但是,结果会是没循环一次,调用一次show函数。然后弹出一个数字。并没有在onclick并没有绑定事件处理函数,因为show函数并没有返回值,或者返回值为undefined ,所以上面的代码应该是这样的
    
    for(var i=0; i<odivs.length; i++){
        show(i);
        odivs[i].onclick = undefined;
    }
    
    
    好吧,以上是我在学习过程中犯的一些错误,和自己慢慢深入后的一些理解。下面正式来说说解决这个问题的办法。
    
    
    
    上面说过:
    1.要想把i的值保存下来。必须在执行过程中把i放到一个函数中,并且函数需要立即执行。
    2.函数的返回值应该是一个事件处理函数。否则事件绑定就没有意义了。
    
    
    修改后代码如下。
 

  
    for(var i=0; i<odivs.length; i++){
        
        odivs[i].onclick = show(i); // 讲变量i传入函数立即执行,保留下来。
    
    }
    
    function show(i){
        
        return function(){  //返回一个事件处理函数。函数内部调用alert的方法将i显示出来。
            alert(i);
        }
        
    }

 


    
    //结果 0,1,2 正确。
    
    
    然后我们写在一起
    
    通过一个佚名函数实现,道理和上面一样。
    
  

 for(var i=0; i<odivs.length; i++){
        
        odivs[i].onclick = (function(i){
                
                return function(){
                        alert(i);
                    }
                
            })(i);
    
    }

 


    
    当然还可以这么写
    
    先把i保留下来,再绑定事件。顺序反一下。
    
 

  for(var i=0; i<odivs.length; i++){
        
        (function(i){
            
            odivs[i].onclick = function(){
                alert(i);
            }
            
        })(i);
        
    }

 


    
至此,这个问题处理完毕。


文章中很多来自 http://www.zhihu.com/question/20019257 ,感谢!
百度搜了很多这个问题,排前面的都是几个li,然后出现这个问题,然后就是一个解决办法,而且大多数网站都是这样,抄来抄去,连标点都一样,很让人困惑,浪费事件。


上面这篇内容对我启发很大。讲讲自己的理解。

我来讲讲自己的理解。

首先说了,JS其实也分传值和传地址 也就是 by value and by sharing. 对应java,C等语言的 by value 和 by reference


var bar;
var foo = bar;
bar = {'key' : 'value'};
console.log(foo , bar );

这样一个案例。

输出的是 undefined 和 {'key':value};

从这里可以看出,foo=bar 是把 bar的值传递给了 foo ; 因为一开始 bar没有赋值,值是undefined,所以foo也是undefined,bar再赋值。

假如foo=bar 是将 bar的引用传递给了 foo的话,那么 修改bar的值, foo输出的也应该是修改后的值。

再看如下代码

var d = new Date();
console.log(d.getMinutes());
var a = d;
a.setMinutes(18);
console.log(d.getMinutes());

// 18

测试的时候是 48 18

很明显, a = d 时,是将 引用传递给了 a ,修改a的属性时,d也跟着变了。



看JS中的作用域(scope) 看原文,已经写的不错了。
http://www.zhihu.com/question/20019257

 

个人的一点心得,还在学习中,如有不对的地方,还望指出。

posted on 2013-05-16 14:02  专注前端  阅读(880)  评论(0编辑  收藏  举报

导航