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 相关文章推荐
javascript学习随笔(使用window和frame)的技巧
Mar 08 Javascript
js判断是否为数组的函数: isArray()
Oct 30 Javascript
JQueryEasyUI Layout布局框架的使用
Apr 08 Javascript
jQuery基本过滤选择器使用介绍
Apr 18 Javascript
fmt:formatDate的输出格式详解
Jan 09 Javascript
js封装可使用的构造函数继承用法分析
Jan 28 Javascript
通过实例理解javascript中没有函数重载的概念
Jun 03 Javascript
javascript性能优化之事件委托实例详解
Dec 12 Javascript
Jquery技巧(必须掌握)
Mar 16 Javascript
详解使用vue脚手架工具搭建vue-webpack项目
May 10 Javascript
详解vue组件开发脚手架
Jun 15 Javascript
vue单页应用在页面刷新时保留状态数据的方法
Sep 21 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异常处理技术,顶级异常处理器
2012/06/13 PHP
PHP得到mssql的存储过程的输出参数功能实现
2012/11/23 PHP
PHP容易忘记的知识点分享
2013/04/30 PHP
php的ajax简单实例
2014/02/27 PHP
php判断数组元素中是否存在某个字符串的方法
2014/06/14 PHP
PHP删除数组中指定值的元素常用方法实例分析【4种方法】
2018/08/21 PHP
JavaScript函数作用域链分析
2015/02/13 Javascript
jquery实现Ctrl+Enter提交表单的方法
2015/07/21 Javascript
javascript中substring()、substr()、slice()的区别
2015/08/30 Javascript
基于javascript实现动态时钟效果
2020/08/18 Javascript
基于javascript实现页面加载loading效果
2020/09/15 Javascript
Bootstrap实现带暂停功能的轮播组件(推荐)
2016/11/25 Javascript
js实现固定宽高滑动轮播图效果
2017/01/13 Javascript
20行js代码实现的贪吃蛇小游戏
2017/06/20 Javascript
vue计算属性和监听器实例解析
2018/05/10 Javascript
[03:57]DOTA2英雄梦之声_第03期_幻影刺客
2014/06/21 DOTA
[43:24]VG vs Serenity 2018国际邀请赛小组赛BO2 第二场 8.17
2018/08/20 DOTA
python批量下载图片的三种方法
2013/04/22 Python
在Apache服务器上同时运行多个Django程序的方法
2015/07/22 Python
将Django项目部署到CentOs服务器中
2018/10/18 Python
pyside+pyqt实现鼠标右键菜单功能
2020/12/08 Python
python实现集中式的病毒扫描功能详解
2019/07/09 Python
在django中使用post方法时,需要增加csrftoken的例子
2020/03/13 Python
在keras中model.fit_generator()和model.fit()的区别说明
2020/06/17 Python
如何完美的建立一个python项目
2020/10/09 Python
英国顶级足球鞋的领先零售商:Lovell Soccer
2019/08/27 全球购物
2014的自我评价
2014/01/13 职场文书
《蚂蚁和蝈蝈》教学反思
2014/02/24 职场文书
浪漫的婚礼主持词
2015/06/30 职场文书
三好学生主要事迹怎么写
2015/11/03 职场文书
2016保送生自荐信范文
2016/01/29 职场文书
浅谈CSS不规则边框的生成方案
2021/05/25 HTML / CSS
使用CSS实现小三角边框原理解析
2021/11/07 HTML / CSS
为Centos安装指定版本的Docker
2022/04/01 Servers
Win11怎么把合并的任务栏分开 Win11任务栏合并分开教程
2022/04/06 数码科技
Redis中key的过期删除策略和内存淘汰机制
2022/04/12 Redis