在所有現(xiàn)代瀏覽器當(dāng)中——除了IE9之前的版本——都實(shí)現(xiàn)了DOM2標(biāo)準(zhǔn)事件模型旋恼,這個事件模型規(guī)定昼牛,每一個DOM元素所觸發(fā)的事件泛鸟,都要經(jīng)歷三個階段:一蝠咆、捕獲階段;二北滥、目標(biāo)對象本身的事件處理程序調(diào)用階段刚操;三、冒泡階段碑韵。
冒泡階段:當(dāng)文檔元素上發(fā)生某個類型的事件時赡茸,他們會在文檔樹上向上傳播。(即調(diào)用父元素相同類型的事件處理函數(shù))祝闻。
捕獲階段:捕獲階段像反向的冒泡階段占卧。最先調(diào)用window對象的捕獲處理程序,然后是Document對象的捕獲處理程序联喘,接著是body對象的华蜒,再然后是DOM樹向下,以此類推豁遭,直到調(diào)用事件目標(biāo)元素的父元素的捕獲處理程序叭喜。
在目標(biāo)元素對象本身上注冊的捕獲事件處理程序,不會被調(diào)用
蓖谢。在DOM2標(biāo)準(zhǔn)事件模型中捂蕴,為一個DOM元素綁定事件的方法為addEventListener(),這個方法要求傳遞三個參數(shù):
1、第一個參數(shù)為一個字符串闪幽,表示事件類型啥辨,如"click";
2盯腌、第二個參數(shù)是一個函數(shù)溉知,表示事件處理程序,瀏覽器會默認(rèn)為該函數(shù)傳遞一個事件對象(Event);
3腕够、第三個參數(shù)是一個布爾值级乍,布爾值為false,表示此函數(shù)將注冊為冒泡事件處理程序(通常設(shè)置為false即可)帚湘,如果值為true玫荣,表示此函數(shù)將注冊為捕獲事件處理程序。值得注意的是大诸,能通過多次調(diào)用addEventListener ()為同一個對象注冊同一事件類型的多個處理程序函數(shù)崇决。當(dāng)對象上發(fā)生事件時材诽,所有該事件類型的注冊處理程序,都會按照注冊的順序調(diào)用
恒傻。
分析事件的觸發(fā)過程,分兩種情況:
- 當(dāng)目標(biāo)元素不存在父元素或目標(biāo)元素的父元素并沒有注冊與觸發(fā)目標(biāo)元素相同類型的事件時建邓,事件模型的第一和第三個節(jié)點(diǎn)是沒有實(shí)際意義的(即不會發(fā)生任何事情)盈厘;
- 當(dāng)目標(biāo)元素存在父元素且目標(biāo)元素的父元素注冊了與觸發(fā)目標(biāo)元素相同類型的事件時,事件模型的第一個和第三個階段就開始起作用了:
為了方便官边,先將以下示例中頁面效果截圖如下沸手,所有示例的頁面效果都是如此:
html中頁面效果
html:
<div id="eventSpreadDiv" style="padding:20px;background-color:red;width:300px;">
<button id="eventSpreadBtn" type="button">eventSpreadBtn</button> eventSpreadDiv
</div>
(1) 父元素注冊捕獲事件,子元素不注冊事件:
js:
document.getElementById("eventSpreadDiv").addEventListener("click", function(e) {console.log(e.currentTarget.id);}, true);
控制臺輸出:
點(diǎn)擊div(紅色區(qū)域):
eventSpreadDiv
點(diǎn)擊button:
eventSpreadDiv
分析:
當(dāng)點(diǎn)擊div時注簿,出現(xiàn)的是事件觸發(fā)過程的情況1:目標(biāo)元素不存在父元素契吉,所以只執(zhí)行了注冊在eventSpreadDiv上的click方法,此時e.currentTarget就是eventSpreadDiv诡渴;
當(dāng)點(diǎn)擊button時捐晶,出現(xiàn)的是事件觸發(fā)過程的情況2:目標(biāo)元素(此時是button)的父元素(div)注冊了與觸發(fā)目標(biāo)元素相同類型的事件(click事件),又因?yàn)閐iv上注冊的click事件的類型為捕獲事件妄辩,所以當(dāng)事件模型進(jìn)行第一階段時棍辕,先執(zhí)行了div的click事件溺森,此時e.currentTarget就是eventSpreadDiv;然后執(zhí)行第二階段,即目標(biāo)元素button的click事件(并無注冊事件)电媳;接下來是第三階段冒泡階段(也沒有相應(yīng)的函數(shù)調(diào)用)。
(2)父元素注冊冒泡事件萄涯,子元素不注冊事件:
js:
document.getElementById("eventSpreadDiv").addEventListener("click", function(e) {console.log(e.currentTarget.id);}, false);
控制臺輸出:
點(diǎn)擊div(紅色區(qū)域):
eventSpreadDiv
點(diǎn)擊button:
eventSpreadDiv
分析:當(dāng)點(diǎn)擊div時陈肛,出現(xiàn)的是事件觸發(fā)過程的情況1:目標(biāo)元素不存在父元素,所以只執(zhí)行了注冊在eventSpreadDiv上的click方法楞黄,此時e.currentTarget就是eventSpreadDiv池凄;當(dāng)點(diǎn)擊button時,出現(xiàn)的是事件觸發(fā)過程的情況2:目標(biāo)元素(此時是button)的父元素(div)注冊了與觸發(fā)目標(biāo)元素相同類型的事件(click事件)谅辣,又因?yàn)閐iv上注冊的click事件的類型為冒泡事件修赞,所以事件模型進(jìn)行第一階段時沒有相應(yīng)的函數(shù)調(diào)用;然后執(zhí)行第二階段桑阶,即目標(biāo)元素button的click事件(并無注冊事件)柏副;接下來是第三階段冒泡階段,此時執(zhí)行了div的click事件蚣录,e.currentTarget就是eventSpreadDiv割择。
(3)父元素注冊捕獲事件和冒泡事件,子元素注冊click事件
js:
document.getElementById("eventSpreadBtn").addEventListener("click", function(e){console.log(e.currentTarget.id)}, false);
document.getElementById("eventSpreadDiv").addEventListener("click", function(e) {console.log("冒泡" + e.currentTarget.id);}, false);
document.getElementById("eventSpreadDiv").addEventListener("click", function(e) {console.log("捕獲" + e.currentTarget.id);}, true);
控制臺輸出:
點(diǎn)擊div(紅色區(qū)域):
冒泡eventSpreadDiv
捕獲eventSpreadDiv
點(diǎn)擊button:
捕獲eventSpreadDiv
eventSpreadBtn
冒泡eventSpreadDiv
分析:當(dāng)點(diǎn)擊div時萎河,出現(xiàn)的是事件觸發(fā)過程的情況1:目標(biāo)元素不存在父元素荔泳,所以只執(zhí)行了注冊在eventSpreadDiv上的click方法蕉饼,但是,此時div被注冊了兩個click方法玛歌,所以昧港,click事件其實(shí)被觸發(fā)了兩次,調(diào)用順序?yàn)榇a的注冊順序(即先調(diào)用第一個被注冊的冒泡事件再調(diào)用第二個被注冊的捕獲事件)支子,這兩個事件執(zhí)行時的e.currentTarget都是div创肥;當(dāng)點(diǎn)擊button時,出現(xiàn)的是事件觸發(fā)過程的情況2:目標(biāo)元素(此時是button)的父元素(div)注冊了與觸發(fā)目標(biāo)元素相同類型的事件(click事件)值朋,又因?yàn)閐iv上既注冊了事件類型為click的捕獲事件叹侄,又注冊了事件類型為click的冒泡事件,所以當(dāng)事件模型進(jìn)行第一階段時昨登,執(zhí)行了div的click捕獲事件趾代,此時e.currentTarget就是eventSpreadDiv;然后執(zhí)行第二階段丰辣,即目標(biāo)元素button的click事件撒强,此時e.currentTarget就是button;接下來是第三階段冒泡階段糯俗,執(zhí)行了div的click冒泡事件尿褪,此時e.currentTarget就是eventSpreadDiv。
總結(jié):DOM2標(biāo)準(zhǔn)事件模型得湘,為我們編寫JavaScript事件驅(qū)動型的程序提供了更靈活的方案杖玲,比如我們可以在捕獲階段和冒泡階段來阻止事件的繼續(xù)傳播(詳情請看上一篇),也可利用這一模型進(jìn)行代碼的調(diào)試淘正。