溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JS動態(tài)添加元素及綁定事件造成程序重復(fù)執(zhí)行解決

發(fā)布時間:2020-09-15 19:06:01 來源:腳本之家 閱讀:135 作者:closertb 欄目:web開發(fā)

前言

本文主要給大家分享一下前段時間遇到的bug,這個Bug是關(guān)于jquery 的on方法綁交互事件,類似于$('#point').on('click','.read-more',function () {})這樣的代碼造成的程序重復(fù)執(zhí)行,很多人在文章里寫到了,也說了用off方法來解綁,但都未能點出問題的本質(zhì),幾乎都忽略了問題的本質(zhì)其實是事件委托造成的。

話不多說,上點天天看到的代碼:

第一種:  

 $(document).on('click', function (e) {
 consol.log('jquery事件綁定')
 });

第二種:  

 document.addEventListener('click',function (e) {
 consol.log('原生事件綁定')  
 });

第三種:  

 var id = setInterval(function () {
 console.log('定時器循環(huán)事件綁定')
 },1000);

上面的代碼,相信不少同盟,天天都會寫到,看似簡單的事件綁定,卻經(jīng)常能給我們帶來意想不到的結(jié)果,特別是在這個SPA,應(yīng)用AJAX頁面局部刷新如此盛行的時代。

那什么是事件綁定,造成的程序重復(fù)執(zhí)行呢?這個事情要說清除,好像不是那么簡單,還是用一段測試代碼來說明吧。你可以拷貝到本地,自己試試: 

<!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('事故發(fā)生點');
 })
/*  setInterval(function () {
  console.log('fdfdfg');
  },2000);*/
  /*用冒泡來綁定事件,類似于Jquery的on綁定事件*/
 /* document.querySelector('body').addEventListener('click',function (e) {
  if(e.target.classList.contains('read-more')){
   alert('事故發(fā)生點');
  }
  })*/

 }
 } ;
 document.querySelector('.add_but').addEventListener('click',function (e) {
 example.getData();
 e.stopImmediatePropagation();
 });
</script>
</body>
</html>

以上是我為說清這個事情寫的一段測試代碼,可以拷貝下來試試。當(dāng)我們點擊頁面的按鈕,觸發(fā)調(diào)用example.getData()這個函數(shù),模擬ajax獲取數(shù)據(jù)成功后,就會根據(jù)局部刷新頁面內(nèi)元素類名為point的內(nèi)容,同時會為加載這個內(nèi)容中的read-more A標(biāo)簽綁定一個事件,就這樣我們想要的效果出現(xiàn)啦,當(dāng)元素第一次加載時,頁面正常,‘事故發(fā)生點'彈出一次,當(dāng)二次刷新觸發(fā)后,你會發(fā)現(xiàn)其彈出了兩次,當(dāng)?shù)谌螘r,你會發(fā)現(xiàn),其彈三次,以此類推。。。。

OMG,這個程序到底怎么了,我明明每次事件綁定前,前面綁定的元素都刪除了,為什么,被刪除的尸體感覺還在動作,好吧,上面就是我第一次遇到這個情況發(fā)出的感嘆。

最后是問身邊的大神,才突然領(lǐng)悟,原來綁定一直都在,而這個綁定被保存在一個叫做事件隊列的地方,他不在循環(huán)執(zhí)行的主線程中,畫了一張需要默契才能看懂的圖,勉強(qiáng)看一看。

JS動態(tài)添加元素及綁定事件造成程序重復(fù)執(zhí)行解決

事件隊列

還原真相

其實上面那一段代碼是為了測試而特意寫的代碼,除了定時器外,其他兩個點擊事件換個正常的寫法,重復(fù)執(zhí)行的情況是不會出現(xiàn)的,正常的代碼:  

 // jquery 事件直接綁定的寫法;
 $('#point .read-more').on('click',function () {
  alert('事故發(fā)生點');
 })
 // 原生JS 事件直接綁定的寫法;
 document.querySelector('.read-more').addEventListener('click',function (e) {
  alert('事故發(fā)生點');
 })

看出差別了嗎?其實就是不用冒泡來事件委托,而是直接給添加的元素綁定事件。所以Dom事件是講道理的,動態(tài)添加的元素,再動態(tài)為此綁定事件,待元素被刪除后,與其綁定的相應(yīng)事件其實是會從事件綁定隊列中刪除的,而非如上面測試代碼,給人的感覺是元素移除后,但其綁定的事件還在內(nèi)存中。但請記住,這是個誤會,上面測試的代碼之所以給人這種錯覺,是因為我們并沒有為動態(tài)添加的元素綁定事件,而僅僅是用了事件委托的形式,實際上事件是綁定在#point元素上的,其一直存在,利用事件冒泡來讓程序知道我們點擊了動態(tài)添加的鏈接元素。測試中特意用原生js去重現(xiàn)了這次事件委托,jquery的on綁定事件其實原理基本相同。  

document.querySelector('body').addEventListener('click',function (e) {
 if(e.target.classList.contains('read-more')){
  alert('事故發(fā)生點');
 }
})

解除bug的那些方法

定時器

這個是最易犯的錯誤,當(dāng)然也是最易解的錯誤,因為設(shè)定定時器時,其會返回一個數(shù)值,這個數(shù)值應(yīng)該是事件隊列此定時器中的一個編號吧,類似于9527;步驟就是設(shè)定一個全局變量來保持這個返回值id,在每次設(shè)定定時器時,先通過id清除已經(jīng)設(shè)定過的定時器    

 clearInterval(intervalId); //粗暴的寫法
 intervalId&&clearInterval(intervalId); //嚴(yán)謹(jǐn)?shù)膶懛? intervalId=setInterval(function () {
  console.log('fdfdfg');
  },2000); 

Dom事件

其實上面我們已經(jīng)說過,最直接的辦法就是不采用事件委托,而是采用直接綁定;如果確實要用事件委托來綁定事件,那就是解綁。在jquery中提供了unbind函數(shù)來解綁事件,不過在jquery 1.8版本以后,這個方法已經(jīng)不推薦了,而是推薦off方法。比如上面的on事件委托的方式,要解綁,可采用語句$('#point').off('click','.read-more')

有缺陷的解決方案,添加flag

很好理解,第一次綁定后,flag置位,下一次在執(zhí)行這個綁定時,程序就知道在這個節(jié)點上已經(jīng)有了綁定,無需再添加,具體操作就是:  

 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('事故發(fā)生點'+data.content);
  });
  flag = true;
 }
 };

從邏輯上,看起來沒有問題,但仔細(xì)觀察,發(fā)現(xiàn)這是有問題的。當(dāng)我們第二次,第三次刷新時,彈出框的內(nèi)容還是和第一次模擬刷新后點擊后彈出的內(nèi)容一致,還是'事故發(fā)生點df1',而非和內(nèi)容一樣遞增,為什么呢,感覺事件隊列里面的回調(diào)函數(shù)被單獨保存起來了,data被深拷貝了,而不再是一個引用。確實有點難理解,我也不知道到底是為什么,如果哪位能說清楚,還請一定告知。

結(jié)個尾寫在最后,其實平常寫一些程序時,事件綁定,造成程序重復(fù)執(zhí)行這些情況很少發(fā)生,其通常會出現(xiàn)在我們寫插件的時候,插件需要適應(yīng)多種調(diào)用環(huán)境,所以在插件內(nèi)部做到防止事件重復(fù)綁定的情況非常重要。

總結(jié)

以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,如果有疑問大家可以留言交流,謝謝大家對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI