jQuery动态添加元素与事件委托导致重复执行的原理及解决方案
本文深入解析jQuery中使用on方法进行事件委托时,动态添加元素导致事件重复绑定的问题。通过测试代码揭示事件队列机制,指出根本原因在于事件绑定在父元素而非动态子元素。文章提供了包括使用off解绑、直接绑定、定时器清理及标志位控制在内的多种解决方案,并分析了各自的优缺点与潜在陷阱。
前言与问题背景
前言
本文主要分享一个近期遇到的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 id = setInterval(function () {
console.log('定时器循环事件绑定')
},1000);
上述代码在开发中十分常见,看似简单的事件绑定,在SPA(单页应用)或AJAX局部刷新场景下,常引发意想不到的结果。
事件重复执行的原理分析
那么,什么是事件绑定导致的程序重复执行?要讲清楚这一点并不容易,以下通过一段测试代码进行说明,读者可自行拷贝本地验证:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<button class="add_but">点击</button>
<div id="point">fdfsdf
</div>
<script src="https://cdn.bootcss.com/jquery/1.8.3/jquery.js"></script>
<script>
var count=1;
var example = {
getData:function () {
var data ={
content:'df'+count++,
href:''
};
this.renderData(data);
},
renderData:function (data) {
document.getElementById('point').innerHTML='<div>this is a '+data.content+'点此<a class="read-more" href="javasript:;" rel="external nofollow" rel="external nofollow" >查看更多</a></div>';
$('#point').on('click','.read-more',function () {
alert('事故发生点');
})
/* setInterval(function () {
console.log('fdfdfg');
},2000);*/
/*用冒泡来绑定事件,类似于Jquery的on绑定事件*/
/* document.querySelector('body').addEventListener('click',function (e) {
if(e.target.classList.contains('read-more')){
alert('事故发生点');
}
})*/
}
} ;
document.querySelector('.add_but').addEventListener('click',function (e) {
example.getData();
e.stopImmediatePropagation();
});
</script>
</body>
</html>
以上测试代码旨在阐明问题。当点击页面按钮触发example.getData()函数,模拟AJAX获取数据成功后,会根据局部刷新后页面内类名为point的元素内容,为其中加载的read-more A标签绑定事件。效果看似正常:首次加载时,页面正常,‘事故发生点’弹出一次;但二次刷新触发后,弹窗出现两次;第三次则出现三次,依此类推。
这究竟是怎么回事?明明每次事件绑定前,之前绑定的元素都已被删除,为何被删除的元素仿佛仍在‘动作’?这是初次遇到此情况时的困惑。
最终请教专家后恍然大悟:绑定操作一直存在,且该绑定被保存在一个名为事件队列的地方,它并不在循环执行的主线程中。绘制了一张需要一定默契才能理解的示意图,供参考。

事件队列机制
还原真相
上述测试代码是特意为了演示而编写的。除了定时器外,若将两个点击事件改为正常写法,重复执行的情况便不会出现。正常的代码写法如下:
// jquery 事件直接绑定的写法;
$('#point .read-more').on('click',function () {
alert('事故发生点');
})
// 原生JS 事件直接绑定的写法;
document.querySelector('.read-more').addEventListener('click',function (e) {
alert('事故发生点');
})
看出区别了吗?关键在于不使用冒泡进行事件委托,而是直接为添加的元素绑定事件。因此,DOM事件是有逻辑的:动态添加元素并动态为此绑定事件,当元素被删除后,与其绑定的相应事件会从事件绑定队列中移除。这与上述测试代码给人的错觉不同,测试代码中给人的感觉是元素移除后,其绑定的事件仍残留在内存中。但请记住,这是一个误会。测试代码之所以产生这种错觉,是因为我们并未为动态添加的元素直接绑定事件,而是使用了事件委托形式。实际上,事件是绑定在#point元素上的,该元素一直存在,利用事件冒泡机制让程序感知到对动态添加链接元素的点击。测试中特意使用原生JS重现了这次事件委托,jQuery的on绑定事件原理基本相同。
document.querySelector('body').addEventListener('click',function (e) {
if(e.target.classList.contains('read-more')){
alert('事故发生点');
}
})
解决方案与代码示例
解除Bug的那些方法
定时器方案
这是最容易犯的错误,也是最容易解决的错误。因为设定定时器时会返回一个数值,这应当是事件队列中该定时器的编号,类似9527。步骤是:设定一个全局变量来保存这个返回值id,在每次设定定时器时,先通过id清除已设定的定时器。
clearInterval(intervalId); //粗暴的写法
intervalId&&clearInterval(intervalId); //严谨的写法
intervalId=setInterval(function () {
console.log('fdfdfg');
},2000);
Dom事件方案
如前所述,最直接的办法是不采用事件委托,而是采用直接绑定。如果确实需要使用事件委托来绑定事件,则必须进行解绑。在jQuery中,提供了unbind函数来解绑事件,不过在jQuery 1.8版本以后,该方法已不推荐,转而推荐使用off方法。例如,对于上述on事件委托的方式,要解绑,可采用语句$('#point').off('click','.read-more')。
有缺陷的解决方案:添加flag标志
这很好理解,第一次绑定后,将flag置位,下次执行此绑定时,程序便知道该节点上已有绑定,无需再添加。具体操作如下:
var flag = false;
var example = {
getData: function () {
var data = {
content: 'df' + count++,
href: ''
};
this.renderData(data);
},
renderData: function (data) {
document.getElementById('point').innerHTML = '<div>this is a ' + data.content + '点此<a class="read-more" href="javasript:;" rel="external nofollow" rel="external nofollow" >查看更多</a></div>';
!flag && $('#point').on('click', '.read-more', function () {
alert('事故发生点'+data.content);
});
flag = true;
}
};
从逻辑上看似乎没有问题,但仔细观察会发现存在缺陷。当我们第二次、第三次刷新时,弹出框的内容仍与第一次模拟刷新后点击弹出的内容一致,即仍是'事故发生点df1',而非随内容递增。这是为何?感觉事件队列里面的回调函数被单独保存起来了,data被深拷贝了,而不再是一个引用。这确实有点难理解,具体原因尚不明确,若有高手能解释清楚,请务必告知。
最后总结,其实平常编写程序时,事件绑定造成程序重复执行的情况很少发生。这通常出现在编写插件时,因为插件需要适应多种调用环境,所以在插件内部做到防止事件重复绑定的情况非常重要。
总结
以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作具有一定的参考学习价值。如果有疑问大家可以留言交流,谢谢大家对本站的支持。
JS怎么实现页面强制刷新
2026-11-05 14:53:38
点击JS链接直接运行不下载?前端实现.js文件强制下载的三种方法
2026-11-05 12:12:23
怎么封装一个通用的JS创建元素辅助函数
2026-11-05 11:41:41
Swiper.js 怎么监听滑动结束并获取当前索引
2026-11-05 10:57:15
HTML、CSS和JS的关系:Web前端三件套怎么协作
2026-11-04 15:41:40
