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