JS动态添加元素及绑定事件造成程序重复执行解决

在这里记录下遇到的一个bug,这个Bug是关于jquery 的on方法绑定事件,类似于$('#point').on('click','.read-more',function () {})这样的代码造成的程序重复执行,很多人在文章里写到了,也说了用off方法来解绑,但都未能点出问题的本质,几乎都忽略了问题的本质其实是事件委托造成的。

事件绑定

我们平时用到的事件绑定基本是以下三种:

  • 第一种
$(document).on('click', function (e) {
  consol.log('jquery事件绑定')
});
  • 第二种
document.addEventListener('click',function (e) {
  consol.log('原生事件绑定')  
});
  • 第三种
var timer = setInterval(function () {
  console.log('定时器循环事件绑定')
},1000);

那什么是事件绑定造成的程序重复执行呢?这个事情要说清楚,好像不是那么简单,还是用一段测试代码来说明吧:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .mask {
      display: none;
    }
    .mask_con {
       80%;
      margin-left: 10%;
      height: 80px;
      border: 1px solid #ccc;
      display: flex;
      align-items: center;
      justify-content: space-around;
    }
  </style>
</head>
<body>
  <button class="buy_btn">购买</button>
  <button class="more_btn">更多</button>
  <div class="mask">
    <div class="mask_con"></div>
  </div>

  <script src="./js/jquery-2.2.4.min.js"></script>
  <script>
    $(document).on("click", ".closeMask", function (e) {
      e.preventDefault();
      $(".mask").hide();
    })
    $(document).on("click", ".buy_btn", function (e) {
      e.preventDefault();
      showMask("buy");
    })
    $(document).on("click", ".more_btn", function (e) {
      e.preventDefault();
      showMask("more");
    })

    function showMask(text) {
      if (text == "buy") {
        $(".mask_con").html(`
          <button class="btn">去购买</button>
          <button class="closeMask">关闭</button>
          `)
      } else if (text == "more") {
        $(".mask_con").html(`
          <button class="btn">查看更多</button>
          <button class="closeMask">关闭</button>
          `)
      }
      $(".mask").show();
      $(document).on("click", ".btn", function (e) {
        e.preventDefault();
        if (text == "buy") {
          alert("去购买");
        } else if (text == "more") {
          alert("查看更多")
        }
      })
    }
  </script>
</body>
</html>

我们看下这段代码的效果:

当我们点击页面上的按钮时,会出现弹窗,点击弹窗中的按钮,会alert不同的内容。但当我们多次点击出现弹窗后,alert也弹出了一次、两次、三次...
明明是每次事件绑定前,mask_con元素中的内容都更新了,相当于之前绑定的元素都不在了,为什么像是被删除的元素依然有事件绑定。
查阅后,突然反应过来,原来绑定一直都在,而这个绑定被保存在一个叫做事件队列的地方,他不在循环执行的主线程中,画了一张需要默契才能看懂的图,勉强看一看。

如果我们不使用冒泡来事件委托,而是直接给添加的元素绑定事件,是不会有问题的。

$(".btn").on("click",function(e) {
  e.preventDefault();
  if(text == "buy") {
    alert("去购买");
  }else if(text == "more") {
    alert("查看更多")
  }
})

所以Dom事件是讲道理的,动态添加的元素,再动态为此绑定事件,待元素被删除后,与其绑定的相应事件其实是会从事件绑定队列中删除的,而非如上面测试代码,给人的感觉是元素移除后,但其绑定的事件还在内存中。但请记住,这是个误会,上面测试的代码之所以给人这种错觉,是因为我们并没有为动态添加的元素绑定事件,而仅仅是用了事件委托的形式,实际上事件是绑定在document元素上的,其一直存在,利用事件冒泡来让程序知道我们点击了动态添加的按钮元素。

解除绑定的方法

  • 定时器中的事件绑定:
    设定一个全局变量来保存这个定时器,在每次设定定时器时,先清除已经设定过的定时器。
clearInterval(timer); //粗暴的写法
timer&&clearInterval(timer); //严谨的写法
timer=setInterval(function () {
  console.log('定时器');
},2000); 
  • DOM中的事件绑定:
    其实上面我们已经说过,最直接的办法就是不采用事件委托,而是采用直接绑定;
    如果确实要用事件委托来绑定事件,那就是解绑。
    在jquery中提供了unbind函数来解绑事件,不过在jquery 1.8版本以后,这个方法已经不推荐了,而是推荐off方法。
    比如上面的on事件委托的方式,要解绑,可采用语句$(document).off('click','.btn') 。
原文地址:https://www.cnblogs.com/ZerlinM/p/14061568.html