天天看点

循环一个节点列表(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

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