闭包应用之循环事件绑定的N种解决办法

92 阅读1分钟
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>珠峰在线Web高级课</title>
</head>

<body>
    <!-- 在结构上设定自定义属性index,存储按钮的索引 -->
    <button index='0'>我是第1个按钮</button>
    <button index='1'>我是第2个按钮</button>
    <button index='2'>我是第3个按钮</button>
</body>

</html>

1、这种方式肯定是不能实现的,因为i是全局的变量,循环结束之后,i变成了button.length 为3

<script>
    var buttons = document.querySelectorAll('button'); //=>NodeList“类数组”集合
    for (var i = 0;i < buttons.length;i++) {
        buttons[i].onclick = function () {
            console.log(`当前点击按钮的索引:${i}`);
        };
    } 
</script>

2、方案一:基于“闭包”的机制完成 「每一轮循环都产生一个闭包,“存储对应的索引”;点击事件触发,执行对应的函数,让其上级上下文是闭包即可」

var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
    // 每一轮循环都会形成一个闭包,存储私有变量i的值(当前循环传递的i的值)
    //   + 自执行函数执行,产生一个上下文EC(A)  私有形参变量i=0/1/2
    //   + EC(A)上下文中创建一个小函数,并且让全局buttons中的某一项占用创建的函数
    (function (i) {
        buttons[i].onclick = function () {
            console.log(`当前点击按钮的索引:${i}`);
        };
    })(i);
} 

3、方案二:跟方案一一样

var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
    buttons[i].onclick = (function (i) {
        return function () {
            console.log(`当前点击按钮的索引:${i}`);
        };
    })(i);
}

4、基于LET这种玩法也是“闭包”方案

let buttons = document.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {
    buttons[i].onclick = function () {
        console.log(`当前点击按钮的索引:${i}`);
    };
} 


解析:浏览器在每一轮循环的时候,帮助我们形成的“闭包”

let buttons = document.querySelectorAll('button');
for (let i = 0; i < buttons.length; i++) {
    // 父级“快级上下文”:控制循环
    //   第一轮循环  私有的块级上下文EC(B1)  i=0
    //   ->当前上下文中创建的一个小函数,被全局的按钮的CLICK占用了,EC(B1)不会被释放 “闭包”
    buttons[i].onclick = function () {
        console.log(`当前点击按钮的索引:${i}`);
    };
} 

------------------------以上三种解决方案都是闭包的原理----------------------
5、自定义属性 「性能强于闭包」

var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
    // 每一轮循环都给当前按钮(对象)设置一个自定义属性:存储它的索引
    buttons[i].myIndex = i;
    buttons[i].onclick = function () {
        // this -> 当前点击的按钮
        console.log(`当前点击按钮的索引:${this.myIndex}`);
    };
} 

6、 事件委托 「比之前的性能提高40%-60%」

  • 不论点击BODY中的谁,都会触发BODY的点击事件

  • ev.target是事件源:具体点击的是谁

       document.body.onclick = function (ev) {
           var target = ev.target,
               targetTag = target.tagName;
           // 点击的是BUTTON按钮
           if (targetTag === "BUTTON") {
               var index = target.getAttribute('index');
               console.log(`当前点击按钮的索引:${index}`);
           }
       };