[Javascript]:DOM綁定事件、事件流機制、事件委託、事件對象

本章節涉及到的知識點都是以DOM標準爲主,所以可能會和IE瀏覽器不兼容,請見諒。

事件

        事件是指發生的事情。在網頁中,事件通常是指用戶或瀏覽器自身執行的某種動作,也可以表示爲文檔和瀏覽器窗口發生的交互行爲。

事件處理程序

        也叫事件句柄、事件處理函數。它用於表示事件發生時要執行的操作。響應事件的處理函數就是事件處理程序。例如觸發一個按鈕的onclick事件,瀏覽器便會執行相應的事件處理程序,執行什麼完全取決於你的Javascript代碼。

簡單的事件實例

 <script>
        function test(){
            alert("警告一下");
            if(confirm("想關閉網頁嗎")){
                window.close(); //關閉當前窗口
            }
        }        
 </script>
    <button onclick="test()">點擊我</button>
當單擊按鈕時,觸發onclick事件,然後執行它的事件處理程序:test()。這種添加事件方式就像HTML元素中的樣式屬性一樣,和HTML標籤耦合度太高。怎麼改變呢?這裏就需要提到綁定事件的幾種方式和DOM事件模型。


DOM事件模型和綁定事件

  • DOM的事件模型分爲DOM0、DOM2、和DOM3三個級別。因爲DOM1沒有定義和事件相關的內容,所以DOM1不存在。DOM3則是在DOM2的基礎上,給事件做了一些分類,還提供了自定義事件,當然,我這裏的只是提供大概知識,至於它們的具體細節還需要你自己去查閱相關資料。
  • 綁定事件:事件和事件處理程序建立聯繫,觸發事件,則執行相應事件處理函數。 也可以叫事件處理程序和事件監聽,雖然它們的說法和寫法不同,但本質都是讓一個事件綁定一個事件處理函數,所以我更喜歡用綁定這個詞彙。在後面會提及以上各種說法,別把它們搞成不同概念。


DOM0的兩種綁定事件方式

一. HTML屬性綁定事件

事件作爲DOM元素節點的屬性,屬性值是事件處理程序。

 <button onclick="alert('HTML屬性綁定事件');">點擊</button>

這裏,字符串形式的事件處理程序的就是Javascript代碼,只要符合規定,就會被執行。就像eval()函數一樣。如有多個語句,就用分號隔開。

HTML屬性綁定事件的缺點

        JS代碼和HTML代碼緊密相連,修改其中之一,另一個也要修改;存在頁面加載問題,當頁面數據過多,如有大量圖片,而<script>腳本放在頁面底部,頁面還沒加載完成時就觸發事件,引發錯誤。所以,這種方式已經被基本淘汰了。能不用就不用。


二. Javascript綁定事件

把一個函數名賦值給一個事件屬性。也就是把函數指針賦值給事件。
格式:elementNode.event = 函數名/匿名函數。
    <button id="btn">點擊我</button>
    <script>
        var btn = document.getElementById("btn"); //獲取元素節點
        function test1(){
            alert("單擊按鈕");
        }
        btn.onclick = test1; //函數名後別加括號,那相當於執行該函數然後返回函數值。
        //匿名函數形式 
        btn.onmouseout = function(){
            alert("鼠標離開按鈕");
        }
    </script>

除了上面使用普通函數聲明和匿名函數來綁定事件,還能用Function對象的構造函數來動態編譯函數並執行,但綁定事件時不推薦使用。


DOM0解除綁定事件

        爲什麼要解除綁定事件呢?爲了避免重複綁定。當然,在實際中,解除綁定事件用得較少,但也不是沒用。解除方式:例如:btn.onclick = null ,讓一個事件指向空指針,表示該事件無效,相當於讓它找不到事件處理函數,自然就不會執行。

Javascript綁定事件與HTML屬性綁定事件比較:

        相比於HTML屬性綁定事件,這種方式更簡單直觀,而且每個瀏覽器都支持,可以跨瀏覽器使用。Javascript代碼和HTML標籤分離,結構和行爲分開。就算HTML代碼先於JS代碼加載完成,相應事件也要等待JS代碼執行完後纔會綁定。

DOM0事件的細節問題

        每個事件只能綁定一個事件處理函數,如果同類事件再次綁定,後面的會覆蓋前面。就和JS函數一樣,多個同名函數只會執行一個,且是最後一個(這裏不討論JS函數重載機制)。DOM0級的事件名都是on + 事件名,這和DOM2綁定事件時用的事件名稱稍有不同,關於DOM2,後面會說到。
   <!-- DOM0級事件的同類型事件觸發情況 -->
    <button id="btn" onclick="alert('HTML屬性綁定的事件onclick');">點擊我</button>
    <script>
        var btn = document.getElementById("btn"); //獲取元素節點
        function test1(){
            alert("JS綁定第一個");
        }
        btn.onclick = test1;
        btn.onclick = function(){
            alert("JS綁定第二個");
        }
    </script>
輸出結果:JS綁定第二個 ,驗證了DOM0級的事件只能調用一個事件處理函數的說法。



DOM2的綁定事件方式

DOM2事件模型的綁定事件也叫作事件監聽,很符合方法名。這裏只提供DOM的標準方法。

elementNode.addEventListener(event, function, useCapture)          添加事件監聽
elementNode.removeEventListener(event, function, useCapture)    移除事件監聽
參數說明

event :事件名稱,去掉了on,例如”onclick“,填寫時填‘’click“ 。

function:事件處理函數。

useCapture:是否在捕獲階段觸發事件。true表示捕獲階段,false表示冒泡階段。

添加事件監聽實例

        function listen(){
            alert("事件監聽");
        }
        //給元素節點添加事件監聽
        btn.addEventListener("click", listen, false); //冒泡階段
        btn.addEventListener("click", listen, true); //捕獲階段

移除事件監聽實例

btn.removeEventListener("click", listen, false);

注意:添加事件監聽的事件處理函數不能是匿名函數,因爲找不到事件處理函數,所以沒法移除。


除了常規用法,還可以只填入事件名和事件處理函數兩個參數,這時就和DOM0的事件一樣,默認使用冒泡階段來調用事件處理函數。

        btn.addEventListener("click", function (){
            console.log("按鈕");
        }); //等同於添加false
        div1.addEventListener("click", function (){
            console.log("div1層");
        },false);


相比於DOM0事件,DOM2的事件允許多次綁定。簡要來說,就是一個節點上的同類型事件可以觸發多次。

 var btn = document.getElementById("btn"); 
        btn.addEventListener("mouseover", function(){
            alert("第一次放上去");
        });
        btn.addEventListener("mouseover", function(){
            alert("第二次放上去");
        });
        btn.addEventListener("mouseover", function(){
            alert("第三次放上去");
        });

關於DOM2事件的綁定事件和移除事件方式已介紹完成,但其中涉及的冒泡階段和捕獲階段又是什麼?這就是我們下面要了解的重點。


DOM事件流機制和事件委託

事件流就是某個DOM節點觸發事件時,事件會在DOM樹的節點中一層一層的傳遞,這就形成了“流”。流具有方向,所以事件流就是觸發事件的順序。

JavaScript高級程序設計(第三版)關於事件流有一個很形象的比喻:

       可以想象畫在一張紙上的一組同心圓,如果你把手指放在圓心上,那麼你的手指指向的其實不是一個圓,而是紙上所有的圓。換句話說,在單擊按鈕的同時,你也單擊了按鈕的容器元素,甚至也單擊了整個頁面。


DOM2將事件流分爲三個階段:捕獲階段、處於目標階段、冒泡階段。

  • 捕獲階段:從window對象出發,然後層層往下傳遞,直到目標節點事件之前(從外到內)。
  • 目標階段: 目標節點的觸發事件。
  • 冒泡階段:從觸發事件的目標節點的父節點開始,層層往上傳遞,直到window對象(從內到外)。DOM0事件的觸發都在冒泡階段。

用網上一張圖來生動地說明這三個階段:


事件流實例

 <div id="div1">     
        <button id="btn">點擊</button>
    </div>
    <script>
        var btn = document.getElementById("btn");
        var dvi1 = document.getElementById("div1"); //定位元素節點
        //捕獲階段 
        window.addEventListener("click", function (){
            console.log("window");
        }, true);
        document.addEventListener("click", function (){
            console.log("document");
        }, true);
        btn.addEventListener("click", function (){
            console.log("按鈕");
        }, true);
        div1.addEventListener("click", function (){
            console.log("div1層");
        }, true);
        //冒泡階段  這裏沒用false,因爲默認使用冒泡
        window.addEventListener("click", function (){
            console.log("window");
        });
        document.addEventListener("click", function (){
            console.log("document");
        });
        btn.addEventListener("click", function (){
            console.log("按鈕");
        });
        div1.addEventListener("click", function (){
            console.log("div1層");
        });
    </script>   

通過實例,可以看出事件流的捕獲階段和冒泡階段兩者的觸發事件順序是相反的。而且事件觸發後,在事件傳遞過程中,存在同類型事件的都會被激活。但有些事件是不應該被觸發的,所以我們要阻止事件繼續傳播。


阻止事件傳播

調用事件對象的stopPropagation()方法,可以阻止該事件繼續傳播到其他DOM節點上,就是說之後經過的節點不會再觸發該類事件,在事件傳播的任何階段都能調用。

注意,它阻止的只是同類事件不再傳播,並不會阻止其他類型事件的觸發。

【上面提到了事件對象,這裏先不介紹,後面會講到】

以冒泡階段爲例,在div節點處調用事件對象的stopPropagation()

        //冒泡階段
        document.addEventListener("click", function (){
            console.log("document");
        });
        btn.addEventListener("click", function (){
            console.log("按鈕");
        });
        div1.addEventListener("click", function (event){
            console.log("在div處停止傳播");
            event.stopPropagation(); //在此處阻止事件傳播
        });
        執行結果:
        按鈕
        div處停止傳播
如果換成捕獲階段,那麼結果就是document 、 div。 


事件委託

委託的意思:將自己的事務囑託他人代爲處理。放在這裏理解就是:通過別的DOM節點來綁定事件,從而管理當前節點上的事件。

事件委託工作原理:根據DOM事件流的冒泡階段,當子節點觸發事件,會冒泡到父節點的相同事件上,所以可以通過父節點來監控子節點的冒泡事件。

不使用事件委託和事件委託的對比:

    <script>
        //傳統的循環綁定方式,批量綁定事件不推薦 id
        var ul = document.getElementById("ul_1");
        var li_obj = ul.getElementsByTagName("li");//拿到子節點中所有的li標籤節點
        for(var i = 0; i < li_obj.length; i++){
            li_obj[i].addEventListener("click", function(){
                alert(this.childNodes[0].nodeValue); //拿到觸發事件的節點對象的子文本節點
            });
        }
        //事件委託方式
        var ul = document.getElementById("ul_1");
        ul.addEventListener("click", function(event){
           var target = event.target; //拿到事件源。也就是觸發該事件的節點
           alert(target.childNodes[0].nodeValue);
        }); 
    </script>

觀察這兩個實例,雖然看起來差別不大,但當子節點越多越來,綁定事件就成了負擔。如果這些子節點頻繁刪除和新增時,事件還沒辦法動態綁定,所以需要改進。

而使用事件委託則不用擔心這些問題,通過事件冒泡,當父節點觸發相同事件時,只需通過事件對象獲取觸發事件的真正來源。就可以針對觸發事件的節點作相應處理。事件對象獲取事件源方式:event.target。


事件對象

event對象在事件第一次觸發時創建,並且一直伴隨着事件在DOM節點結構中流轉,直到生命週期結束。也就是說,事件源只有一個,這也是爲什麼事件委託中可以準確找到事件目標節點。
event對象會被作爲事件處理函數的第一個參數傳遞給事件處理函數中。
event對象用於記錄觸發事件的一些信息,但是不同的事件對象具有不同的信息,例如鍵盤事件對象可以獲取按了什麼鍵。

event對象的常用屬性和方法:

bubbles                     返回布爾值,指示事件是否是冒泡事件類型。                     
eventPhase                返回事件傳播的當前階段。
currentTarget             返回其事件監聽器觸發該事件的元素。   
target                         返回觸發此事件的元素(事件的目標節點)。 
type                           返回當前 Event 對象表示的事件的名稱。
preventDefault()         通知瀏覽器不要執行與事件關聯的默認動作。
stopPropagation()      停止事件傳播,不再派發事件。


阻止瀏覽器的默認事件行爲

當觸發某些事件時,瀏覽器會有自身的默認執行方式,例如最常見的a標籤,如果我們只想點擊鏈接拿到地址,但卻不想跳轉到該鏈接的目標URL,所以我們需要阻止事件的傳播。這裏就用到了事件流和事件對象知識。但這次除了提到事件對象的stopPropagation()方法,還介紹了額外兩種方式:

  • event.preventDefault()
  • event.stopPropagation()
  • return false
preventDefault()         阻止特定事件的默認行爲(只有 cancelable 設置爲 true 的事件纔可以使用)。
stopPropagation()      停止事件在DOM節點中傳播。不會阻止默認行爲。
return false                 之後所有相關的觸發事件都不會被執行。阻止事件繼續傳播,事件冒泡和默認行爲都被阻止。

點擊超鏈接後阻止瀏覽器跳轉

    <a href="前端學習/img/girl.png">獲取地址不跳轉</a>
    <p>顯示目標地址</p>
    <script>
        var atest = document.querySelector("a");
        var p = document.querySelector("p");
        atest.onclick = function(event){
            var attr = this.getAttribute("href");
            p.innerHTML = attr;
            return false;
        }
    </script>

禁用右鍵菜單

 window.onload = function(){
      document.oncontextmenu = function(){
           return false;
       }
 }


關於DOM事件基本涉及到的知識到此介紹完成,其中大量的知識都來源網上,鄙人做的也只是以個人的理解將其整理一番。當然,這些都是些基礎,不怎麼深入。如果你能從中學習到知識,那將是極好的,如果其中有不足之處或出現錯誤,請給予指正,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章