为什么闭包可以解决js中循环绑定事件的问题?

PenguinGoHack
  • 227

今天遇到了利用循环给元素绑定事件的问题,相信大家应该都遇到过这个问题。

我的代码

var hint = ["必填,长度为4-18个字符", "", "再次输入相同密码", "格式: example@github.com", ""];
    var inputIDs = ["ip-name", "ip-pwd", "ip-pwd-cfm", "ip-email", "ip-phone"];
    for (var i = 0; i<hint.length; i++) {
        document.getElementById(inputIDs[i]).addEventListener("focus", function (e) {
            var tar = e.target.parentElement.getElementsByClassName("alert")[0];
            tar.innerHTML = hint[i];
        });
    }
    

下面这段代码用闭包来解决的,但是我没看懂,为什么这样调用不会调用到循环结束时i的值了呢?
没有理解这里闭包的用法,有哪位可以解释一下吗?

for (var i = 0; i < 5; i++) { 
    var a = function(v){
        return function(){
            console.log(v)
        }
    }
    document.body.addEventListener('click',a(i))
}
回复
阅读 3.5k
8 个回答
scort
  • 8.4k
✓ 已被采纳

如果你不使用闭包,你引用的i就是全局作用域(看你的代码没有在一个函数里)里的i。document.getElementById(inputIDs[i])这里的i就是你绑定时候的i,但是当你调用绑定的事件的时候函数体里的i就不再是绑定时候的i而是循环执行完以后的i的值了,其实你能访问到循环执行完了的i的值也有点类似于闭包,函数执行完了,你还能访问还是里声明的变量。
但是你使用了闭包就不一样了,document.body.addEventListener('click',a(i))这里把i以参数的形式传入a函数,这时候已经和循环里的i不一样了,他们指向了不同的地址只是值一样,如果这里你传入的参数是个对象类型,闭包函数里的参数就是你这个对象的引用的copy,你在循环下面修改了传入对象的属性,闭包函数里也会相应修改,但是修改对象的引用的话,闭包里则不会改变。

i是全局变量,focus含有异步函数,当异步被调用时,i已经被修改了。
只需要让i复制给一个局部变量即可

老徐不二
  • 1.3k

click是一个异步操作,在事件执行的时候,i的值已经是5

改成下面就好了

var hint = ["必填,长度为4-18个字符", "", "再次输入相同密码", "格式: example@github.com", ""];
    var inputIDs = ["ip-name", "ip-pwd", "ip-pwd-cfm", "ip-email", "ip-phone"];
    for (var i = 0; i<hint.length; i++) {
        document.getElementById(inputIDs[i]).addEventListener("focus", (function (i) {
            return function(e){
                var tar = e.target.parentElement.getElementsByClassName("alert")[0];
                tar.innerHTML = hint[i];
            };
        })(i));
    }
wujer
  • 35

需要知悉作用域链的知识并且理解闭包的概念。
先看你的代码:
var tar = e.target.parentElement.getElementsByClassName("alert")[0];tar.innerHTML = hint[i];这段代码是focus事件处理函数,只有在出发focus事件时才会执行。循环结束后,绑定了hint.length个事件处理函数,当这些处理函数执行时,他们都访问的是i变量,注意是同一个i变量,所以结果都一样。
再来看看闭包:
利用闭包的特点,当i传入时,闭包函数能够访问参数v,此时v保存的是i的值,也就是说闭包函数存储了这个i值,在需要的时候可以进行访问。

我对闭包函数的理解就是给return的那个匿名函数开辟一片私有的空间,可以存放一些私有的变量或者函数。为什么不能在函数内部直接声明一些变量呢?因为函数内部的变量只有在函数执行期间才存在,那么变量上的值是变化的,而闭包函数的这篇私有空间会一直存在,这也是为什么说它会消耗内存的原因。

当循环结束以后i的值为length,所以每次都取不到值

你的代码里,focus的回调函数再循环结束前并不会执行,等到回调执行的时候,i已经是hint.length了.
闭包的方法,每次循环都会执行a(i),把当前的值传到a中,从而保存了这一轮i的值。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏