zoukankan      html  css  js  c++  java
  • 循环一个节点列表(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

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

  • 相关阅读:
    数据库优化
    2013调试sql的方法
    C++ Primer Pluse_6_课后题
    软件测试--(10)功能测试、系统测试
    软件测试--发展之路
    软件测试--(9)软件测试过程和软件测试模型
    软件测试--(8)软件开发过程和软件开发模型
    软件测试--(7)集成测试
    软件测试--(6)模块测试(单元测试)
    软件测试--(5)测试策略
  • 原文地址:https://www.cnblogs.com/iu90/p/3081724.html
Copyright © 2011-2022 走看看