JS中動態添加元素並綁定事件,形成程序重複執行

那些莫名奇妙的bug

歇了兩週沒寫點什麼了,感受最近有點知識慌,分享一下前段時間遇到的bug,這個Bug是關於jquery 的on方法綁交互事件,相似於$('#point').on('click','.read-more',function () {})這樣的代碼形成的程序重複執行,不少人在文章裏寫到了,也說了用off方法來解綁,但都未能點出問題的本質,幾乎都忽略了問題的本質實際上是事件委託形成的。話很少說,上點每天看到的代碼:
第一種: css

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

第二種: html

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

第三種: java

var id = setInterval(function () {
        console.log('定時器循環事件綁定')
   },1000);複製代碼

上面的代碼,相信很多同盟,每天都會寫到,看似簡單的事件綁定,卻常常能給咱們帶來意想不到的結果,特別是在這個SPA,應用AJAX頁面局部刷新如此盛行的時代。那什麼是事件綁定,形成的程序重複執行呢?這個事情要說清除,好像不是那麼簡單,仍是用一段測試代碼來講明吧。你能夠拷貝到本地,本身試試: jquery

<!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:;">查看更多</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,這個程序到底怎麼了,我明明每次事件綁定前,前面綁定的元素都刪除了,爲何,被刪除的屍體感受還在動做,好吧,上面就是我第一次遇到這個狀況發出的感嘆。
最後是問身邊的大神,才忽然領悟,原來綁定一直都在,而這個綁定被保存在一個叫作事件隊列的地方,他不在循環執行的主線程中,畫了一張須要默契才能看懂的圖,勉強看一看。
ajax

事件隊列
事件隊列

還原真相

其實上面那一段代碼是爲了測試而特地寫的代碼,除了定時器外,其餘兩個點擊事件換個正常的寫法,重複執行的狀況是不會出現的,正常的代碼: bash

// 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')。ui

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

很好理解,第一次綁定後,flag置位,下一次在執行這個綁定時,程序就知道在這個節點上已經有了綁定,無需再添加,具體操做就是: this

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:;">查看更多</a></div>';
            !flag && $('#point').on('click', '.read-more', function () {
                alert('事故發生點'+data.content);
            });
            flag = true;
        }
    };複製代碼

從邏輯上,看起來沒有問題,但仔細觀察,發現這是有問題的。當咱們第二次,第三次刷新時,彈出框的內容仍是和第一次模擬刷新後點擊後彈出的內容一致,仍是'事故發生點df1',而非和內容同樣遞增,爲何呢,感受事件隊列裏面的回調函數被單獨保存起來了,data被深拷貝了,而再也不是一個引用。確實有點難理解,我也不知道究竟是爲何,若是哪位能說清楚,還請必定告知

結個尾

寫在最後,其實日常寫一些程序時,事件綁定,形成程序重複執行這些狀況不多發生,其一般會出如今咱們寫插件的時候,插件須要適應多種調用環境,因此在插件內部作到防止事件重複綁定的狀況很是重要。本文首發於:closertb.site版權全部,歡迎保留原文連接進行轉載:)

相關文章
相關標籤/搜索