素材牛VIP会员
这是js闭包问题吗?
 pa***in  分类:JavaScript  人气:1034  回帖:4  发布于6年前 收藏

讨论这个帖子(4)垃圾回帖将一律封号处理……

Lv7 码师
Sl***rk 软件测试工程师 6年前#1

因为你每次for循环的时候,给按钮添加了点击事件,同时btn也会指向不同的按钮,在最后一次循环的时候,btn指向了最后一个按钮,所以每次输出的都是4

Lv6 码匠
st***en 交互设计师 6年前#2
this.value; // 试试
Lv2 入门
青***f 页面重构设计 6年前#3

题主的 for 循环跑完后,实际上:

i=5
btn = 最后一个 input

为什么这样呢,我们来看下代码,稍微改了下:

for(var i=0;i<5;i++){
    var btn=document.createElement('input');
    btn.setAttribute('type','button');
    btn.setAttribute('value','btn'+i);
    document.body.insertBefore(btn,document.body.firstElementChild); //直接插入到文档最前面,方便测试
    btn.onclick=function(){
        alert(btn.value);
    };
}

要知道 es6 之前是没有块级作用域的,且

用 var 定义的变量会有一个声明提升的特性,这个动作开始于代码执行之前,会将 var 定义的变量提升到所在函数或全局环境的顶端,但是赋值操作仍然停留在原地

题主的代码其实相当于:

var i,btn;
for(i=0;i<5;i++){
    btn=document.createElement('input');
    btn.setAttribute('type','button');
    btn.setAttribute('value','btn'+i);
    document.body.insertBefore(btn,document.body.firstElementChild);
    btn.onclick=function(){
        alert(btn.value);
    };
}
console.log(btn); // btn 被赋值了5次,覆盖了4次,最后一个是 btn4,所以很显然是最后一个
console.log(i);  // i=5的时候跳出循环,所以 i 最后一个值是 5

这样是不是就很好理解了?

因为你给 btn 绑定的事件处理程序(回调函数)并不是马上执行,触发的时候才执行,这个时候 btn 已经被覆盖了4次,变成最后一个 btn 了;

核心原因是: var 有声明提前的特性。如要改的话,有多种方式,这里暂就说两种:
法一:btn 改为 this (推荐)

for(var i=0;i<5;i++){
    var btn=document.createElement('input');
    btn.setAttribute('type','button');
    btn.setAttribute('value','btn'+i);
    document.body.insertBefore(btn,document.body.firstElementChild);
    btn.onclick=function(){
        alert(this.value);
    };
}

在事件绑定中,如果事件处理程序(也就是回调函数)中没有嵌套的函数,事件绑定在谁身上,this 就指向谁,如果回调里还有嵌套函数,this 默认指向全局对象,所以这里 this 就指向了绑定时的那个 btn;不明白戳这
法二:用 let 代替 var

for(var i=0;i<5;i++){
    let btn=document.createElement('input');
    btn.setAttribute('type','button');
    btn.setAttribute('value','btn'+i);
    document.body.insertBefore(btn,document.body.firstElementChild);
    btn.onclick=function(){
        alert(btn.value);
    };
}

这是 ES6 的内容,详细理解戳这:深入理解ES6-中

Lv3 码奴
起***劲 页面重构设计 6年前#4

或许你可以把里面的点击事件包成匿名函数自执行然后传参,也可以应用ES6里面的let 声明循环变量,因为在每一个{}中都是一个let作用域

 文明上网,理性发言!   😉 阿里云幸运券,戳我领取