JS中的事件流

1.事件流

在瀏覽器中,JavaScript和HTML之間的交互是通過事件去實現(xiàn)的,常用的事件有代表鼠標單擊的click事件、代表加載的load事件、代表鼠標指針懸浮的mouseover事件。在事件發(fā)生時,會相對應(yīng)地觸發(fā)綁定在元素上的事件處理程序,以處理對應(yīng)的操作。
通常一個頁面會綁定很多的事件,那么具體的事件觸發(fā)順序是什么樣的呢?
這就會涉及事件流的概念,事件流描述的是從頁面中接收事件的順序。事件發(fā)生后會在目標節(jié)點和根節(jié)點之間按照特定的順序傳播,路徑經(jīng)過的節(jié)點都會接收到事件。我們通過下面的場景來直觀地想象一下事件的流轉(zhuǎn)順序。
頁面上有一個div,分別在body、div、p、span上綁定了click事件。假如我在span上執(zhí)行了單擊的操作,那么將會產(chǎn)生什么樣的事件流呢?

<body>
    <div>
        <p>
            <span>
                文本一
            </span>
        </p>
    </div>
</body>

第一種事件傳遞順序是先觸發(fā)最外層的body元素,然后向內(nèi)傳播,依次觸發(fā)div、p與span元素。
第二種事件傳遞順序先觸發(fā)由最內(nèi)層的span元素,然后向外傳播,依次觸發(fā)p、div與body元素。
第一種事件傳遞順序?qū)?yīng)的是捕獲型事件流,第二種事件傳遞順序?qū)?yīng)的是冒泡型事件流。
一個完整的事件流實際包含了3個階段:事件捕獲階段>事件目標階段>事件冒泡階段。上述兩種類型的事件流實際對應(yīng)其中的事件捕獲階段與事件冒泡階段。


事件流.png
  • 事件捕獲階段
    事件捕獲階段的主要表現(xiàn)是不具體的節(jié)點先接收事件,然后逐級向下傳播,最具體的節(jié)點最后接收到事件。根據(jù)圖中的指示就是Window > Document > html > body > div > p > span。
  • 事件目標階段
    事件目標階段表示事件剛好傳播到用戶產(chǎn)生行為的元素上,可能是事件捕獲的最后一個階段,也可能是事件冒泡的第一個階段。
  • 事件冒泡階段
    事件冒泡階段的主要表現(xiàn)是最具體的元素先接收事件,然后逐級向上傳播,不具體的節(jié)點最后接收事件,根據(jù)圖中的指示就是span > p > div > body > html > Document >Window。
let spanDom = document.querySelector('span');
spanDom .addEventListener("click", function (e) {
    console.info(`span trigger target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
}, false)

let p1Dom = document.querySelector('p');
p1Dom.addEventListener('click', function (e) {
    console.info(`p trigger  target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
})

let divDom = document.querySelector('div');
divDom.addEventListener('click', function (e) {
    console.info(`div trigger  target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
})

let bodyDom = document.querySelector('body');
bodyDom.addEventListener('click', function (e) {
    console.info(`body trigger  target:${e.target.tagName.toLowerCase()} currentTarget:${e.currentTarget.tagName.toLowerCase()} `);
})

當點擊span標簽時候,將輸出


span click

使用addEventListener()函數(shù)綁定的事件在默認情況下,即第三個參數(shù)默認為false時,按照冒泡型事件流處理。第三個參數(shù)為true時,按捕獲型事件流處理,再次點擊span時候會輸出如下:


span click

如果有元素綁定了捕獲類型事件,則會優(yōu)先于冒泡類型事件而先執(zhí)行。

2.事件處理程序

簡單理解事件處理程序,就是響應(yīng)某個事件的函數(shù),例如onclick()函數(shù)、onload()函數(shù)就是響應(yīng)單擊、加載事件的函數(shù),對應(yīng)的是一段JavaScript的函數(shù)代碼。
根據(jù)W3C DOM標準,事件處理程序分為DOM0、DOM2、DOM3這3種級別的事件處理程序。由于在DOM1中并沒有定義事件的相關(guān)內(nèi)容,因此沒有所謂的DOM1級事件處理程序。

  • DOM0級事件處理程序
    DOM0級事件處理程序是將一個函數(shù)賦值給一個事件處理屬性,有兩種表現(xiàn)形式。第一種是先通過JavaScript代碼獲取DOM元素,再將函數(shù)賦值給對應(yīng)的事件屬性。
var btn = document.getElementById("btn"); 
btn.onclick = function(){}

第二種是直接在html中設(shè)置對應(yīng)事件屬性的值,值有兩種表現(xiàn)形式,一種是執(zhí)行的函數(shù)體,另一種是函數(shù)名,然后在script標簽中定義該函數(shù)。

<button onclick="alert('hi');">單擊</button>
<button onclick="clickFn()">單擊</button>
<script>
    function clickFn() {
        alert('hi');
    }
</script>

以上兩種DOM0級事件處理程序同時存在時,第一種在JavaScript中定義的事件處理程序會覆蓋掉后面在html標簽中定義的事件處理程序。
DOM0級事件處理程序只支持事件冒泡階段,一個事件處理程序只能綁定一個函數(shù)。

  • DOM2級事件處理程序
    在DOM2級事件處理程序中,當事件發(fā)生在節(jié)點時,目標元素的事件處理函數(shù)就會被觸發(fā),而且目標元素的每個祖先節(jié)點也會按照事件流順序觸發(fā)對應(yīng)的事件處理程序。DOM2級事件處理方式規(guī)定了添加事件處理程序和刪除事件處理程序的方法。
    在IE10及以下版本中,只支持事件冒泡階段
element.attachEvent("on"+ eventName, handler);         //添加事件處理程序
element.detachEvent("on"+ eventName, handler);         //刪除事件處理程序

在IE11及其他非IE瀏覽器中,同時支持事件捕獲和事件冒泡兩個階段,可以通過addEventListener()函數(shù)添加事件處理程序,通過removeEventListener()函數(shù)刪除事件處理程序。

addEventListener(eventName, handler, useCapture);       //添加事件處理程序
removeEventListener(eventName, handler, useCapture);  //刪除事件處理程序
//其中的useCapture參數(shù)表示是否支持事件捕獲,true表示支持事件捕獲,false表示支持事件冒泡,默認狀態(tài)為false。
  • DOM3級事件處理程序
    DOM3級事件處理程序是在DOM2級事件的基礎(chǔ)上重新定義了事件,也添加了一些新的事件。最重要的區(qū)別在于DOM3級事件處理程序允許自定義事件,自定義事件由createEvent("CustomEvent")函數(shù)創(chuàng)建,返回的對象有一個initCustomEvent()函數(shù),通過傳遞對應(yīng)的參數(shù)可以自定義事件。
    函數(shù)可以接收以下4個參數(shù)。
    · type:字符串、觸發(fā)的事件類型、自定義,例如“keyDown”“selectedChange”。
    · bubble(布爾值):表示事件是否可以冒泡。
    · cancelable(布爾值):表示事件是否可以取消。
    · detail(對象):任意值,保存在event對象的detail屬性中。
    創(chuàng)建完成的自定義事件,可以通過dispatchEvent()函數(shù)去手動觸發(fā),觸發(fā)自定義事件的元素需要和綁定自定義事件的元素為同一個元素。
<body>
<div>
    <p>
            <span>
                自定義事件
            </span>
    </p>
</div>
</body>
<script>
    var customEvent;
    (function () {
        if (document.implementation.hasFeature('CusomEvents', '3.0')) {
            let detailData = {name: 'climber.lee'}
            customEvent = document.createEvent('CustomEvent');
            customEvent.initCustomEvent('myEvent', true, false, detailData)
            let p = document.querySelector('p');
            p.addEventListener('myEvent', function (e) {
                console.info(`p監(jiān)聽到自定義事件執(zhí)行,參數(shù)為:`,e.detail)
            });
            let span = document.querySelector('span');
            span.addEventListener('click', function () {
                p.dispatchEvent(customEvent);
            })
        }
    })();
</script>

點擊span后輸出


自定義事件

3.事件(Event)對象

事件在瀏覽器中是以Event對象的形式存在的,每觸發(fā)一個事件,就會產(chǎn)生一個Event對象。該對象包含所有與事件相關(guān)的信息,包括事件的元素、事件的類型及其他與特定事件相關(guān)的信息。
在Event對象中有兩個屬性總是會引起大家的困擾,那就是target屬性和currentTarget屬性。兩者都可以表示事件的目標元素,但是在事件流中兩者卻有不同的意義。
· target屬性在事件目標階段,理解為真實操作的目標元素。
· currentTarget屬性在事件捕獲、事件目標、事件冒泡這3個階段,理解為當前事件流所處的某個階段對應(yīng)的目標元素。
可以參考第一部分事件流的實例來理解target和currentTarget的區(qū)別
有時我們并不想要事件進行冒泡,可通過stopPropagation和stopImmediatePropagation兩個方法阻止冒泡
· stopPropagation()函數(shù)僅會阻止事件冒泡,其他事件處理程序仍然可以調(diào)用。
· stopImmediatePropagation()函數(shù)不僅會阻止冒泡,也會阻止其他事件處理程序的調(diào)用。
在眾多的HTML標簽中,有一些標簽是具有默認行為的
· a標簽,在單擊后默認行為會跳轉(zhuǎn)至href屬性指定的鏈接中。
那么該如何編寫代碼來阻止元素的默認行為呢?
很簡單,就是通過event.preventDefault()函數(shù)去實現(xiàn)。

4.事件委托

過多事件處理程序”的解決方案是使用事件委托。事件委托利用事件冒泡,可以只使用一個事件處理程序來管理一種類型的事件。例如,click事件冒泡到document。這意味著可以為整個頁面指定一個onclick事件處理程序,而不用為每個可點擊元素分別指定事件處理程序。舉個栗子

<ul id="myLinks">
  <li id="goSomewhere">Go somewhere</li>
 <li id="doSomething">Do something</li>
  <li id="sayHi">Say hi</li>
</ul>

這里的HTML包含3個列表項,在被點擊時應(yīng)該執(zhí)行某個操作。對此,通常的做法是像這樣指定3個事件處理程序

let item1 = document.getElementById("goSomewhere");
let item2 = document.getElementById("doSomething");
let item3 = document.getElementById("sayHi");

item1.addEventListener("click", (event) => {
  location.href = "http:// www.wrox.com";
});

item2.addEventListener("click", (event) => {
  document.title = "I changed the document's title";
});

item3.addEventListener("click", (event) => {
  console.log("hi");
});

如果對頁面中所有需要使用onclick事件處理程序的元素都如法炮制,結(jié)果就會出現(xiàn)大片雷同的只為指定事件處理程序的代碼。使用事件委托,只要給所有元素共同的祖先節(jié)點添加一個事件處理程序,就可以解決問題。

let list = document.getElementById("myLinks");
list.addEventListener("click", (event) => {
  let target = event.target;

  switch(target.id) {
    case "doSomething":
      document.title = "I changed the document's title";
      break;
    case "goSomewhere":
      location.href = "http:// www.wrox.com";
      break;
    case "sayHi":
      console.log("hi");
      break;
  }
});

事件委托具有如下優(yōu)點

  • document對象隨時可用,任何時候都可以給它添加事件處理程序(不用等待DOMContentLoaded或load事件)。這意味著只要頁面渲染出可點擊的元素,就可以無延遲地起作用。
  • 節(jié)省花在設(shè)置頁面事件處理程序上的時間。只指定一個事件處理程序既可以節(jié)省DOM引用,也可以節(jié)省時間。
  • 減少整個頁面所需的內(nèi)存,提升整體性能。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容