js DOM事件模型淺析

事件模型按照DOM規範來說是有兩種:DOM0級事件模型以及DOM2級事件模型。但由於IE瀏覽器的特殊性,IE8及以下瀏覽器不支持DOM2及事件模型,所以需要單獨列出一項IE事件模型。儘管現在IE8及以下版本瀏覽器可以不作爲兼容性考慮對象,但還是有必要說一下。

1.DOM0級事件模型

DOM0級事件模型是最早的事件模型,也叫做原始事件模型,所有瀏覽器都支持。

這種事件模型比較簡單,綁定事件的形式有兩種:

(1)直接在內聯元素上綁定

<body>
    <!-- DOM0級事件模型:1.直接在內聯元素上綁定事件 -->
    <div class="event" οnclick="fun1()">click me</div>
    <script>
        function fun1(){
            alert('you have clicked me !')
        }
    </script>
</body>

(2)在js中獲取元素節點後綁定事件

    <!-- DOM0級事件模型:2.在js中獲取元素節點後綁定事件 -->
    <div class="event">click me</div>
    <script>
        function fun1(e){
            alert('you have clicked me !')
        }
        document.querySelector('.event').onclick = fun1
    </script>

解除事件綁定方法:將元素的綁定事件置空

document.querySelector('.event').onclick = null

DOM0級事件模型的特點是:

(1)每個元素只能綁定一個相同類型事件監聽器函數,如果綁定了多個,那麼後面的會覆蓋前面的。原因是,點擊事件onclick是元素節點對象的一個屬性,屬性值是唯一的。比如,給body元素綁定兩個click事件類型的監聽器函數:document.body.onclick = fun1, document.body.onclick = fun2,但點擊的時候只有fun2會被觸發執行。

(2)沒有捕獲的概念,只有默認的冒泡。我看網上很多介紹DOM0級事件的,說DOM0級事件模型沒有事件流的概念,實際上它是有的,只不過沒有捕獲事件流,只有冒泡事件流,如果類比DOM2級事件模型的說法,DOM0級事件模型包括兩個階段:目標階段、冒泡階段。例子如下:

    <!-- DOM0級事件模型:2.在js中獲取元素節點後綁定事件 -->
    <div class="event">
        click me 
        <button class="button">button</button>
    </div>
    <script>
        function fun1(e){
            console.log('you have clicked me !')
        }
        function fun2(e){
            console.log('clicked button !')
        }
        document.querySelector('.event').onclick = fun1
        document.querySelector('.button').onclick = fun2
    </script>

點擊button按鈕,控制檯打印如上圖所示,先觸發了button按鈕綁定的事件,這是target目標元素,這就是上邊說的目標階段,然後就是冒泡階段,冒泡到父元素,發現父元素也註冊了click事件,那就觸發了這個事件監聽函數。

2. DOM2級事件模型

爲啥直接到DOM2級事件模型了呢,難道沒有DOM1級事件模型嗎?原因是1998年10月1日W3C DOM標準制定了DOM級別1標準,但是1級DOM標準中並沒有定義事件相關的內容,所以沒有所謂的1級DOM事件模型。但是DOM2級標準裏規定了新的事件模型,成爲DOM2級事件模型。

DOM2級事件模型通過addEventListener(eventType,handler,useCapture)、removeEventListener(eventType,handler,useCapture)方法進行添加、刪除事件處理程序。都接受三個參數:

eventType: 事件類型,比如click、mouseover等類型

handler:事件處理程序

useCapture:是否在捕獲階段觸發事件,爲true表示在捕獲階段觸發事件處理程序,爲false表示在冒泡階段觸發事件處理程序

DOM2級事件模型事件流包括三個階段:

(1)捕獲階段:這個階段,事件從Document對象沿着文檔樹向下傳播到目標節點。如果中間任何一個祖先節點註冊了和目標節點相同類型的事件監聽函數,那麼在事件傳播的過程中就會執行這些函數。可以看個例子:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style type="text/css">
        .event{
            color: red;
        }
    </style>
</head>
<body>
    <div class="event">
        click me
        <button class="button">button</button>
    </div>
    <script>
        function fun1(e){
            console.log('you have clicked me !')
        }
        function fun2(e){
            console.log('clicked button !')
        }
         // 開啓捕獲,事件會在捕獲階段被觸發
        document.querySelector('.event').addEventListener('click', fun1, true)
        document.querySelector('.button').addEventListener('click', fun2, true)
    </script>
</body>
</html>

控制檯打印的日誌日誌說明了問題,事件流從HTML節點開始往下捕獲,到div.event節點發現有click類型的事件,就執行綁定的事件監聽函數,所以控制檯先打印‘you have clicked me !’,然後再接着往下,傳播到目標元素button之前都是捕獲階段,直到傳播到目標元素button,此時到了事件流的目標階段,發現有綁定click類型的監聽函數,就會執行該監聽函數,所以控制檯打印‘clicked button !’。然後是從目標節點向上冒泡,但是在冒泡階段都不會觸發任何click類型的事件,因爲addEventListener方法的第三個參數是true。

(2)目標階段:事件處於目標元素自身,直接註冊在目標上的事件監聽函數將執行,如果沒有就不執行。

(3)冒泡階段:這個階段,事件將從目標節點向上傳播回Document對象(與capturing相反的階段)。如果中間任何一個祖先節點註冊了和目標節點相同類型的事件監聽函數,那麼在事件傳播的過程中就會執行這些函數。

    <div class="event">
        click me
        <button class="button">button</button>
    </div>
    <script>
        function fun1(e){
            console.log('you have clicked me !')
        }
        function fun2(e){
            console.log('clicked button !')
        }
        // 關閉捕獲,事件會在冒泡階段被觸發
        document.querySelector('.event').addEventListener('click', fun1, false)
        document.querySelector('.button').addEventListener('click', fun2, false)
    </script>

可以看到控制檯打印的日誌順序與捕獲階段相反,整個過程是:事件流從HTML節點開始往下捕獲,但是所有click事件不會在捕獲階段被觸發,直到目標階段,button元素綁定的事件被觸發,所以控制檯先打印‘clicked button !’,然後向上冒泡,傳播到div.event節點,觸發綁定的事件監聽函數,打印‘you have clicked me !’。

 

阻止事件傳播

如果不想讓事件繼續傳播,可以切斷傳播,方式是調用事件對象的stopPropagation()方法。

    <!-- DOM2級事件模型:阻止事件傳播 -->
    <div class="event">
        click me
        <button class="button">button</button>
    </div>
    <script>
        function fun1(e){
            console.log('you have clicked me !')
        }
        function fun2(e){
            // 阻止事件進一步傳播
            e.stopPropagation()
            console.log('clicked button !')
        }
        // 關閉捕獲,事件會在冒泡階段被觸發
        document.querySelector('.event').addEventListener('click', fun1, false)
        document.querySelector('.button').addEventListener('click', fun2, false)
    </script>

可以看到只觸發了button上的事件,沒有觸發div.event節點上的事件。

DOM2級的Event對象的target和currentTarget屬性

用addEventListener添加的事件監聽函數,在被調用的時候js會傳給他一個Event對象,Event對象有兩個屬性很容易混淆,就是target和currentTarget屬性。target屬性就是指向目標節點,currentTarget屬性是動態的,指向當時觸發事件時的節點,只有處於目標階段時,target和currentTarget才相同,都指向目標節點。還用上面的例子說明:

    <div class="event">
        click me
        <button class="button">button</button>
    </div>
    <script>
        function fun1(e){
            console.log('you have clicked me !')
        }
        function fun2(e){
            console.log('clicked button !')
        }
        // 關閉捕獲,事件會在冒泡階段被觸發
        document.querySelector('.event').addEventListener('click', fun1, false)
        document.querySelector('.button').addEventListener('click', fun2, false)
    </script>

點擊button按鈕,先執行目標節點button的事件監聽函數,此時Event對象的內容如下:

可以看到eventPhase:2表示處於目標階段,此時target和currentTarget都指向目標節點button。然後執行div.event節點的監聽函數,傳入的事件對象如下:

可以看到eventPhase:3表示處於冒泡階段,此時target指向目標節點button,而currentTarget則指向當前正在發生事件的節點div.event節點。

3.IE事件模型

IE事件模型和DOM0級事件模型基本上是一樣的,共有兩個過程:

(1)目標階段

(2)冒泡階段

但是綁定和移除事件的方法是attachEvent(eventType, handler)。

eventType: 事件類型,這裏需要注意,需要加上'on'前綴,比如:'onclick',

handler: 事件處理程序

還有一個地方需要注意:IE不把事件對象傳給事件處理函數,而是作爲window對象的一個屬性,即通過window.event訪問事件對象。

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