[JS]03.循环事件绑定的处理

138 阅读2分钟

1. 闭包的定义作用和弊端

1.1 闭包:函数运行的一种机制(不是某种代码形式)

  • 函数执行会形成一个私有上下文,如果上下文中的某些内容(一般指堆内存地址)被上下文以外的事物(变量/事件绑定等)所占用,则当前上下文不能被出栈释放,这是由浏览器的垃圾回收机制GC决定的,这就是闭包机制(形成了一个不被释放的上下文)。

    • 保护:保护私有上下文中的私有变量和外界互不影响。
    • 保存:上下文不被释放,那么上下文中的私有变量和值都会被保存起来,可以供其下级上下文中使用。
  • 如果大量使用闭包,会导致栈内存太大,页面渲染会变慢,性能受到影响,所以真实项目中需要合理运用闭包,某些代码会导致栈溢出或者内存泄露。

1.2 内存溢出

  • Uncaught RangeError: Maximum call stack size exceeded
// 无退出条件的递归,一直形成私有上下文
function fn(x) {
  console.log(x)
  fn(x+1)
}
fn(1);

2. 循环事件点击

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <!-- button*3{我是第$个按钮} -->
  <!-- 结构上设置自定义属性,存储按钮的索引 -->
  <button index='0'>我是第1个按钮</button>
  <button index='1'>我是第2个按钮</button>
  <button index='2'>我是第3个按钮</button>
  <script src="1.js"></script>
</body>
</html>

2.1 普通处理

  • 执行后,点击每个按钮,结果都是一样
var buttons = document.querySelectorAll('button')
console.log(buttons)  // buttons => NodeList 类数组集合

// buttons是类数组,无数组方法,手写循环
for(var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = function() {
    console.log(`当前点击按钮的索引: ${i}`)
  }
}

image.png

2.2 方案一 基于闭包机制完成

  • 方案一循环多少次产生多少个闭包,损耗性能,减少使用

2.2.1 写法一

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

image.png

2.2.2 写法二

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

// fn中存储的是执行的返回值
var obj = {
  fn: (
    function() {
      console.log('大函数')
      return function() {
        console.log('小函数')
      }
    }
  )()
}
obj.fn()  // 执行的是返回的小函数

2.2.3 写法三

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

2.3 方案二 自定义属性

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

2.4 方案三 事件委托

  • html中标签中添加index属性
// 无论点击哪个body,都会触发当前点击body的click事件
document.body.onclick = function(ev){
  // 事件源
  var target = ev.target;
  targetTag = target.tagName;
  // 通过 tagName 得到的标签名都是大写的
  if(targetTag === "BUTTON") {
    var index = target.getAttribute('index')
    console.log(`当前点击按钮的索引: ${index}`)
  }
};