浅谈js for循环输出i为同一值的问题

时间:2024-01-27 16:55:55

问题再现

​ 最近开发中遇到一个问题,为什么每次输出都是5,而不是点击每个p,就alert出对应的1,2,3,4,5。

<html>  
    <head>  
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
        <title>闭包演示</title> 
    </head>  
    <body>  
        <p>1</p>  
        <p>2</p>  
        <p>3</p>  
        <p>4</p>  
        <p>5</p> 
        <script type="text/javascript">
            window.onload=function() {  
                var ps = document.getElementsByTagName("p");  
                for( var i=0; i<ps.length; i++ ) {  
                    ps[i].onclick = function() {  
                        alert(i);  
                    }  
                }  
            }  
        </script> 
    </body>  
</html>

此时点击任意p弹出的都是5

出现原因:js事件处理器在线程空闲时间不会运行,导致最后运行的时候输出的都是i最后的值,即:5


解决办法:使用闭包将变量i的值保护起来

//sava1:加一层闭包,i以函数参数形式传递给内层函数 
for( var i=0; i<ps.length; i++ ) {  
    (function(arg){   
        ps[i].onclick = function() {   
            alert(arg);  
        };  
    })(i);//调用时参数  
} 


//save2:加一层闭包,i以局部变量形式传递给内存函数 
for( var i=0; i<ps.length; i++ ) {  
    (function () {  
        var temp = i;//调用时局部变量  
        ps[i].onclick = function() {  
            alert(temp);  
        }  
    })();  
}


//save3:加一层闭包,返回一个函数作为响应事件(注意与3的细微区别) 
for( var i=0; i<ps.length; i++ ) {  
    ps[i].onclick = function(arg) {  
        return function() {//返回一个函数  
            alert(arg);  
        }  
    }(i);  
} 


//save4:将变量 i 保存给在每个段落对象(p)上  
for( var i=0; i<ps.length; i++ ) {  
    ps[i].i = i;  
    ps[i].onclick = function() {  
        alert(this.i);  
    }  
}


//save5:将变量 i 保存在匿名函数自身  
for( var i=0; i<ps.length; i++ ) {  
    (ps[i].onclick = function() {  
        alert(arguments.callee.i);  
    }).i = i;  
}   
} 


//save6:用Function实现,实际上每产生一个函数实例就会产生一个闭包
for( var i=0; i<ps.length; i++ ) {  
    ps[i].onclick = new Function("alert(" + i + ");");//new一次就产生一个函数实例 
} 


//save7:用Function实现,注意与6的区别  
for( var i=0; i<ps.length; i++ ) {  
    ps[i].onclick = Function('alert('+i+')'); 
}

总结

在ECMAScript5和6中,var和let的作用域是不同的:var的作用域在函数上,let的作用域在块上,因此当有异步函数发生调用的时候,之前已经绑定好的点击事件会寻找在onload函数的i值,此时i值已经不是当初设定好的值,而是最后一次循环后的值,而使用let的话,那么在每次循环的时候就会在新的块上重新创建新的变量i,所以每次点击的i的值都会不同。