你真的理解事件绑定、事件冒泡和事件委托吗?
一文了解Web API中的事件綁定、事件冒泡、事件委托
- 引言
- 正文
- 一、事件綁定
- 1、事件和事件綁定時什么?
- 2、事件是如何實現的?
- 二、事件冒泡
- 1、事件模型
- 2、事件模型解析
- (1)捕獲階段
- (2)目標階段
- (3)冒泡階段
- 3、addEventListener語法
- 4、事件冒泡和事件捕獲舉例
- (1)事件冒泡
- (2)事件捕獲
- (3)事件捕獲VS事件冒泡
- 三、事件代理(事件委托)
- 四、總結和回顧
- 結束語
引言
事件,是JS Web API比較重要的一個知識點。我們平常所看到的網頁,肯多內容都要用到事件。比如說一個點擊、一個下拉、一個滾動,都要用到事件進行操作。
正文
一、事件綁定
1、事件和事件綁定時什么?
事件,就是可以被 js 捕獲的人為的操作。那什么是人為的操作呢?比如說鼠標的點擊、拖動、縮放網頁等等行為,且在這些行為被 js 捕獲到以后,就是事件。
舉個例子:
比如說我現在要去樓下 喊 舍管阿姨幫我開個門禁,那這個 喊 的操作就是一個事件,就相當于在 js 里喊一個函數去干活。
說完事件,接下來說說事件綁定。
什么是 javascript 的事件綁定呢?
用上面那個例子來繼續闡述。 喊 這個動作是一個事件,那我要怎么樣才能做出 喊 這個動作呢?就需要對我這個動作進行一個綁定。可以通過綁定一個函數,這個函數解決了我怎么喊出來的問題。比如,我要去樓下喊,那這個函數里面就說明了我需要去樓下喊的這個過程。
所以,事件綁定可以理解為,在有一個觸發事件的前提下,后面緊跟著一個事件處理函數,這個函數里面包含著所要執行動作的具體過程等,這就是事件綁定。
接下來我們用代碼來寫一個事件綁定的過程。
function bindEvent(elem, type, fn){elem.addEventListener(type, fn); }const btn1 = document.getElementById('btn1'); bindEvent( btn1 , 'click', event => {console.log(event.target); //event.target為獲取觸發的元素event.preventDefault(); //阻止默認行為alert('clicked'); });瀏覽器顯示效果如下。
大家可以看到,通過點擊按鈕這個事件,獲取到觸發的元素,這就是一個事件綁定。
2、事件是如何實現的?
事件基于發布訂閱模式,就是在瀏覽器加載的時候會讀取事件相關的代碼,但是只有實際等到具體的事件觸發的時候才會執行。
比如點擊按鈕,這是個事件 Event ,而負責處理事件的代碼段通常被稱為事件處理程序 Event Handler ,也就是「啟動對話框的顯示」這個動作。
在 Web 端,我們常見的就是 DOM 事件:
- DOM0 級事件,直接在 html 元素上綁定 on-event ,比如 onclick ,取消的話, dom.onclick = null ,同一個事件只能有一個處理程序,后面的會覆蓋前面的。
- DOM2 級事件,通過 addEventListener 注冊事件,通過 removeEventListener 來刪除事件,一個事件可以有多個事件處理程序,按順序執行,捕獲事件和冒泡事件。
- DOM3級事件,增加了事件類型,比如 UI 事件,焦點事件,鼠標事件。
- UI事件,即當用戶與界面上的元素交互時觸發。
- 焦點事件,即當用元素獲得或失去焦點時觸發。
- 鼠標事件,當用戶通過鼠標在頁面上執行操作時觸發。
二、事件冒泡
1、事件模型
W3C中定義的DOM事件流的發生經歷三個階段:捕獲階段(capturing)、目標階段(targetin)、冒泡階段(bubbling)。
- 冒泡型事件:當你使用事件冒泡時,子級元素先觸發,父級元素后觸發。
- 捕獲型事件:當你使用事件捕獲時,父級元素先觸發,子級元素后觸發。
2、事件模型解析
我們用 W3C 標準的 DOM 事件流模型圖來看事件捕獲、事件冒泡和DOM事件流。
從圖中可以看出,元素事件響應在 DOM 樹中是從頂層的Window開始,流向目標元素(2),然后又從目標元素流向頂層的Window。
通常,我們將這種事件流向分為(1)捕獲階段,(2)目標階段,(3)冒泡階段。-> 序號對應圖中的編號
(1)捕獲階段
捕獲階段是指,事件響應從最外層的Window開始,逐層向內層遞進,直到到達具體的事件目標元素,如上圖中的(1)。同時在捕獲階段,不會處理響應元素注冊的冒泡事件。
(2)目標階段
目標階段指觸發事件的最底層的元素,如上圖中的(2)。
(3)冒泡階段
冒泡階段與捕獲階段相反,事件的響應是從最底層開始一層一層往外傳遞到最外層的Window,即一層一層往上冒,如上圖中的(3)。
3、addEventListener語法
現在,我們知道了 DOM 事件流的三個階段分別是先捕獲階段,然后是目標階段,最后是冒泡階段。這也就是我們平常所看到的一些面試題里面說的先捕獲后冒泡的原因了。到此,相信大家對 DOM 事件流會有一個清晰的了解。
在實際操作中,我們可以通過 element.addEventListener() 函數來設置一個元素的事件模型,具體設置值可以設置為冒泡事件或捕獲事件。
先來看下 addEventListener 函數的基本語法:
element.addEventListener(type, listener, useCapture);其中,三個參數的含義如下:
type:監聽事件類型的字符串;
listener:事件監聽的回調函數,即事件觸發后要處理的函數;
useCapture:默認值為false,表示事件冒泡;當設置為true時,表示事件捕獲。
4、事件冒泡和事件捕獲舉例
接下來我們用幾個實例來運用事件冒泡和事件捕獲。
(1)事件冒泡
先附上一段代碼。
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <style>#a{background-color: darkcyan;line-height: 40px;color: cornsilk;}#b{background-color: chocolate;}#c{background-color: cornflowerblue;} </style> <body><div id="a">事件a<div id="b">事件b<div id="c">事件c</div></div></div><script>let a = document.getElementById('a');let b = document.getElementById('b');let c = document.getElementById('c');//注冊冒泡事件監聽器a.addEventListener('click', () => {console.log("冒泡a")});b.addEventListener('click', () => {console.log('冒泡b')});c.addEventListener('click', () => {console.log("冒泡c")});</script> </body> </html>當我們點擊 事件c 時,瀏覽器執行結果如下:
如我們所預想的,冒泡是從下往上冒泡,所以最終的執行順序為 事件c → 事件b → 事件a ,打印出 冒泡c → 冒泡b → 冒泡a 。
(2)事件捕獲
先附上一段代碼。
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <style>#a{background-color: darkcyan;line-height: 40px;color: cornsilk;}#b{background-color: chocolate;}#c{background-color: cornflowerblue;} </style> <body><div id="a">事件a<div id="b">事件b<div id="c">事件c</div></div></div><script>let a = document.getElementById('a');let b = document.getElementById('b');let c = document.getElementById('c');//注冊捕獲事件監聽器a.addEventListener('click', () => {console.log("捕獲a")}, true);b.addEventListener('click', () => {console.log('捕獲b')}, true);c.addEventListener('click', () => {console.log("捕獲c")}, true);</script> </body> </html>此時,我們給 addEventListener 加上 true 的屬性,因此,當我們點擊 事件c 時,瀏覽器執行結果如下:
如我們所預想的,捕獲是從上往下捕獲,也就是從外層向里層捕獲,所以最終的執行順序為 事件a → 事件b → 事件c ,打印出 捕獲a → 捕獲b → 捕獲c 。
(3)事件捕獲VS事件冒泡
接下來,我們將上述的代碼 事件abc 三個元素都注冊上捕獲和冒泡事件,并以 事件c 作為觸發事件的主體,即事件c為事件流中的目標階段。
附上一段代碼。
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <style>#a{background-color: darkcyan;line-height: 40px;color: cornsilk;}#b{background-color: chocolate;}#c{background-color: cornflowerblue;} </style> <body><div id="a">事件a<div id="b">事件b<div id="c">事件c</div></div></div><script>let a = document.getElementById('a');let b = document.getElementById('b');let c = document.getElementById('c');//注冊冒泡事件監聽器a.addEventListener('click', () => {console.log("冒泡a")});b.addEventListener('click', () => {console.log('冒泡b')});c.addEventListener('click', () => {console.log("冒泡c")});//注冊捕獲事件監聽器a.addEventListener('click', () => {console.log("捕獲a")}, true);b.addEventListener('click', () => {console.log('捕獲b')}, true);c.addEventListener('click', () => {console.log("捕獲c")}, true);</script> </body> </html>當我們點擊 事件c 時,瀏覽器執行結果如下:
如我們所預想的,先對事件進行捕獲,后再對事件進行冒泡。當捕獲時,事件從外往內捕獲,所以打印結果是冒泡是 捕獲a → 捕獲b → 捕獲c 。當冒泡時,事件由內往外冒泡,所以最終的打印結果為 冒泡c → 冒泡b → 冒泡a 。
三、事件代理(事件委托)
講完事件冒泡和事件代理,那么對于事件代理就比較容易理解了。
事件代理,即事件委托。事件代理就是利用事件冒泡或者事件捕獲的機制把一系列的內層元素事件綁定到外層元素上。
我們來看個例子。
<ul id="item-list"><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li> </ul>比如說,我們要給這個 ul 列表下面的每個 li 元素綁定事件。如果按照傳統方法處理的話,我們可能會一個一個去綁定。數據量小的時候可能還好,但如果遇到數據量大的時候呢?一個一個綁定也太可怕了。
因此就有了事件代理。我們可以通過使用事件代理,將綁定多個事件的操作變為只綁定一次的操作,這樣就極大減少了代碼的重復編寫。
因此,利用事件冒泡或事件捕獲,來達到事件代理的效果。具體實現方式如下:
<!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title> </head> <body><ul id="item-list"><li>item 1</li><li>item 2</li><li>item 3</li><li>item 4</li></ul><script>let items = document.getElementById('item-list');//通過事件冒泡實現事件代理items.addEventListener('click', (e) => {console.log('冒泡:click ',e.target.innerHTML)}, false);//通過事件捕獲實現事件代理items.addEventListener('click', (e) => {console.log('捕獲:click ',e.target.innerHTML)}, true);</script> </body> </html>當點擊列表中的 item 時,執行結果如下:
從上圖中可以看出,當點擊目標元素時,可以對其進行捕獲,在捕獲結束后,對其進行冒泡操作,且達到了點擊當前元素只顯示當前元素的效果。
同時,細心的小伙伴已經發現,在我們上面的代碼中,編寫順序是先冒泡后捕獲。但結果打印依然是先捕獲后冒泡。這也就順應了我們上面所說的,關于DOM事件流的順序,都是先捕獲后冒泡,而跟實際的代碼順序是沒有關系的。
四、總結和回顧
講完事件綁定、DOM事件流模型中的事件冒泡和事件捕獲以及事件代理,我們來做個總結和回顧。
(1)以上的內容總結下來有以下幾點:
-
DOM事件流有3個階段:捕獲階段,目標階段,冒泡階段。三個階段的順序為:捕獲階段 → 目標階段 → 冒泡階段。
-
對于目標階段和非目標階段的元素,事件響應執行順序都遵循先捕獲后冒泡的原則。
注:目標階段即當前所點擊事件,即為目標階段。非目標階段即外圍所影響的事件即為非目標階段。
-
事件捕獲是從頂層的Window逐層像內層執行,事件冒泡則相反;
-
事件代理(即事件委托)是根據事件冒泡或事件捕獲的機制來實現的。
(2)用幾個題目來回顧下我們上面所講的知識點
Q1:描述事件冒泡的流程
A1:
- 基于DOM樹形結構
- 事件會順著所觸發的元素,一層一層的往上冒
- 應用場景:事件代理
Q2:當無限下拉圖片列表時,如何監聽每個圖片的點擊?
A2:
- 用事件代理處理
- 用e.target獲取觸發元素
- 用matches來判斷是否觸發元素
結束語
一直都不是特別清楚為什么是事件是先捕獲后冒泡,腦子里也沒有個大概框架,文縐縐的文字也不能讓我對它有所理解。直到看到了 W3C 的那張 DOM 事件流模型的圖,一下子明白了事件為什么是先捕獲后冒泡了。因為 Window 對象是直接面向用戶的,那么當用戶觸發一個事件時,如點擊事件,肯定時從 Window 對象開始的,然后再向內逐層遞進。所以自然也就是先捕獲后冒泡了!
關于Web API中的事件就講到這里啦!如有疑問歡迎評論區評論或私信我交流~
-
關注公眾號 星期一研究室 ,不定期分享學習干貨
-
如果這篇文章對你有用,記得點個贊加個關注再走哦~
總結
以上是生活随笔為你收集整理的你真的理解事件绑定、事件冒泡和事件委托吗?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 提升对前端的认知,不得不了解Web AP
- 下一篇: 你知道304吗?图解强缓存和协商缓存