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 {
width: 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') 。