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


Posted in Javascript onDecember 07, 2017

前言

本文主要给大家分享一下前段时间遇到的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 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标签绑定一个事件,就这样我们想要的效果出现啦,当元素第一次加载时,页面正常,‘事故发生点'弹出一次,当二次刷新触发后,你会发现其弹出了两次,当第三次时,你会发现,其弹三次,以此类推。。。。

OMG,这个程序到底怎么了,我明明每次事件绑定前,前面绑定的元素都删除了,为什么,被删除的尸体感觉还在动作,好吧,上面就是我第一次遇到这个情况发出的感叹。

最后是问身边的大神,才突然领悟,原来绑定一直都在,而这个绑定被保存在一个叫做事件队列的地方,他不在循环执行的主线程中,画了一张需要默契才能看懂的图,勉强看一看。

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

事件队列

还原真相

其实上面那一段代码是为了测试而特意写的代码,除了定时器外,其他两个点击事件换个正常的写法,重复执行的情况是不会出现的,正常的代码:  

// 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被深拷贝了,而不再是一个引用。确实有点难理解,我也不知道到底是为什么,如果哪位能说清楚,还请一定告知。

结个尾写在最后,其实平常写一些程序时,事件绑定,造成程序重复执行这些情况很少发生,其通常会出现在我们写插件的时候,插件需要适应多种调用环境,所以在插件内部做到防止事件重复绑定的情况非常重要。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
给Flash加一个超链接(推荐使用透明层)兼容主流浏览器
Jun 09 Javascript
JavaScript实现列表分页功能特效
May 15 Javascript
js实现文本框选中的方法
May 26 Javascript
关于微信jssdk实现多图片上传的一点心得分享
Dec 13 Javascript
Bootstrap CSS组件之输入框组
Dec 17 Javascript
angular学习之从零搭建一个angular4.0项目
Jul 10 Javascript
BootStrap数据表格实例代码
Sep 13 Javascript
jQuery实现DIV响应鼠标滑过由下向上展开效果示例【测试可用】
Apr 26 jQuery
基于Vue实现平滑过渡的拖拽排序功能
Jun 12 Javascript
解决layui-open关闭自身窗口的问题
Sep 10 Javascript
微信小程序开发摇一摇功能
Nov 22 Javascript
JsonServer安装及启动过程图解
Feb 28 Javascript
Angular2.0/4.0 使用Echarts图表的示例代码
Dec 07 #Javascript
jquery学习笔记之无new构建详解
Dec 07 #jQuery
利用Node.js检测端口是否被占用的方法
Dec 07 #Javascript
禁止弹窗中蒙层底部页面跟随滚动的几种方法
Dec 07 #Javascript
Vue通过URL传参如何控制全局console.log的开关详解
Dec 07 #Javascript
Vue中添加手机验证码组件功能操作方法
Dec 07 #Javascript
react学习笔记之state以及setState的使用
Dec 07 #Javascript
You might like
php MsSql server时遇到的中文编码问题
2009/06/11 PHP
PHP正则匹配日期和时间(时间戳转换)的实例代码
2016/12/14 PHP
php格式文件打开的四种方法
2018/02/24 PHP
jquery 与NVelocity 产生冲突的解决方法
2011/06/13 Javascript
js实现的仿新浪微博完美的时间组件升级版
2011/12/20 Javascript
jquery获取当前点击对象的value方法
2014/02/28 Javascript
js数值和和字符串进行转换时可以对不同进制进行操作
2014/03/05 Javascript
JS根据变量保存方法名并执行方法示例
2014/04/04 Javascript
举例说明如何为JavaScript的方法参数设置默认值
2015/11/17 Javascript
以JavaScript来实现WordPress中的二级导航菜单的方法
2015/12/14 Javascript
JavaScript继承学习笔记【新手必看】
2016/05/10 Javascript
AngularJS表格添加序号的方法
2017/03/03 Javascript
vue使用watch 观察路由变化,重新获取内容
2017/03/08 Javascript
vue项目base64字符串转图片的实现代码
2018/07/13 Javascript
微信小程序使用gitee进行版本管理
2018/09/20 Javascript
Vue项目打包部署到iis服务器的配置方法
2019/10/14 Javascript
jquery css实现流程进度条
2020/03/26 jQuery
weui上传多图片,压缩,base64编码的示例代码
2020/06/22 Javascript
[04:48]DOTA2上海特锦赛小组赛第三日 TOP10精彩集锦
2016/02/28 DOTA
[46:20]TFT vs Secret Supermajor小组赛C组 BO3 第二场 6.3
2018/06/04 DOTA
[19:54]夜魇凡尔赛茶话会 第一期02:看图识人
2021/03/11 DOTA
浅谈Python中range和xrange的区别
2017/12/20 Python
django-rest-swagger对API接口注释的方法
2019/08/29 Python
基于spring boot 日志(logback)报错的解决方式
2020/02/20 Python
Python如何安装第三方模块
2020/05/28 Python
TensorFlow-gpu和opencv安装详细教程
2020/06/30 Python
python中查看.db文件中表格的名字及表格中的字段操作
2020/07/07 Python
Python爬虫与反爬虫大战
2020/07/30 Python
CSS 3.0 结合video视频实现的创意开幕效果
2020/06/01 HTML / CSS
幼师自荐信范文
2013/10/06 职场文书
路政管理专业个人自荐信范文
2013/11/30 职场文书
2014年端午节活动方案
2014/03/11 职场文书
年会搞笑主持词串词
2014/03/24 职场文书
项目经理任命书
2014/06/04 职场文书
计算机毕业生求职信
2014/06/10 职场文书
golang实现浏览器导出excel文件功能
2022/03/25 Golang