十年網站開發(fā)經驗 + 多家企業(yè)客戶 + 靠譜的建站團隊
量身定制 + 運營維護+專業(yè)推廣+無憂售后,網站問題一站解決
想要了解什么是事件捕獲與冒泡,需要先了解什么是事件。
創(chuàng)新互聯公司專注于企業(yè)全網營銷推廣、網站重做改版、開化網站定制設計、自適應品牌網站建設、成都h5網站建設、商城網站定制開發(fā)、集團公司官網建設、外貿網站制作、高端網站制作、響應式網頁設計等建站業(yè)務,價格優(yōu)惠性價比高,為開化等各大城市提供網站開發(fā)制作服務。
什么是事件?
我們知道,在前端開發(fā)中,JavaScript負責定義網頁的“行為”。這里所說的“定義”,其實指的是開發(fā)者可以通過JavaScript語言向瀏覽器描述一些規(guī)則,瀏覽器按照這些規(guī)則與用戶進行交互。比如開發(fā)者希望當用戶點擊頁面上某個按鈕的時候,就彈出一個窗口,顯示特定的內容。而當用戶真正點擊這個按鈕的時候,瀏覽器將按照開發(fā)者定義的這個規(guī)則,去彈出指定的窗口,顯示指定的內容。
在上面的例子中,瀏覽器是一切規(guī)則的執(zhí)行者,開發(fā)者是這些規(guī)則的制定者,而JavaScript只是開發(fā)者向瀏覽器描述這些規(guī)則時所使用的的語言(否則瀏覽器無法知道開發(fā)者想要在什么情況下做什么事)。假如我們通過以下的語句向瀏覽器描述了一條規(guī)則:
頁面上現在有一個按鈕,我們首先使用原生DOM獲取這個按鈕,然后使用button.addEventListener(“click”, function(){})這樣的語法向瀏覽器描述了一條規(guī)則:當這個按鈕被點擊(click)時,彈出提示框,顯示“我被點擊了”。用戶點擊按鈕后網頁就會出現如下提示:
瀏覽器把這次“點擊”稱為一個“事件”。“事件”用于描述交互過程中某些特定的關鍵點(如點擊、鼠標滑動、滾輪滾動、按下鍵盤、觸屏操作等,每個操作都對應特定的事件,不過事件也可能與用戶行為無關,比如網頁加載完畢也是一個事件)。而瀏覽器處理交互最重要的手段就是基于事件來執(zhí)行開發(fā)者定義好的回調函數(如在用戶“點擊按鈕”時“彈出窗口”,而定義“彈出窗口”行為的就是回調函數,也就是addEventListener中的function)。
定義完這條規(guī)則,當用戶點擊按鈕時,瀏覽器就會彈出上述窗口了。我們稱“點擊”這個事件是在這個按鈕上觸發(fā)的(因為我們的回調函數是綁定在這個按鈕上的)。
那什么是事件的捕獲與冒泡呢?
事件的捕獲與冒泡
這個問題與HTML的結構息息相關。
在前端開發(fā)中,我們使用標簽語言HTML來描述網頁結構,如一個標題、一個段落、一個表格等,這些網頁元素描述了網頁上有哪些需要顯示的內容,它們構成了整個網頁的“骨骼”,通常是一種嵌套的結構,比如:
... //這是對網頁內容的元描述 //這是網頁需要渲染的真正內容標題
這里是一個段落
上述網頁結構示意圖如下(在沒有設置padding等屬性的情況下,子元素通常會填滿父元素,這里的內間距只是為了說明元素的嵌套關系):
我們看到,body元素是整個網頁的容器,它的內部包含了一個div元素,而div的內部又包含了兩個元素:h2和p。假如我們現在在p的內部點擊了一下,那么請問我們有沒有點擊它的外部容器div,以及最外部的body呢?
從瀏覽器的角度來看,我們同時在點擊這三個元素。
想要證明這個結論非常簡單,只需要使用addEventListener向div和body各自綁定click事件,如果點擊p時也會被觸發(fā),那就說明上面的結論是正確的。毫無疑問,它們會被觸發(fā)。
那么問題來了,既然用戶同時在點擊這三個元素,瀏覽器應該先執(zhí)行哪個元素定義的回調函數呢(由于JavaScript采用單線程模型,執(zhí)行回調函數必然有一定的先后順序)?
這個問題實際上是在說,對于嵌套的元素,應該從內向外還是從外向內響應事件。瀏覽器之爭的兩大對立方分別有自己的看法:Netscape公司認為應當由最外層的body首先得到這個事件,其次是div,最后才是目標元素p;而微軟的IE開發(fā)組則認為,應當是內部的p首先得到這個事件,然后是div,最后才是body。在沒有標準約束的情況下,兩者按照自己的想法去設計瀏覽器的事件模型,Netscape從外向內傳播的模型在業(yè)內被稱為事件捕獲模型,而微軟從內向外傳播的模型則被稱為事件冒泡模型。
兩個模型雖然從思路上南轅北轍,但是都可以保證所有綁定的回調函數正確觸發(fā)(不過觸發(fā)順序是相反的。如果這個觸發(fā)順序很重要,那么在當時,你的代碼可能只能在一個瀏覽器中正確運行,或者去做惡心的瀏覽器兼容)。不過瀏覽器允許開發(fā)者在事件傳播的過程中阻止事件的繼續(xù)傳播,此時兩者的差異就變得極其明顯。
假如我們在定義點擊div元素的回調函數時阻止了事件的傳播:
div.addEventListener("click", function(e){ ... e.stopPropagation(); //阻止事件繼續(xù)傳播 })
這個代碼會在兩種模型下產生巨大的差異。在捕獲模型中,由于最外部首先得到該事件,因此body的點擊事件首先被觸發(fā),之后是div的點擊事件。由于阻止了事件傳播,p元素不會觸發(fā)回調。而在冒泡模型中則恰恰相反,內部的p首先得到該事件,其次才是div,因此觸發(fā)回調的將是p和div,body因為事件沒有冒泡上來而無法監(jiān)聽到該事件。同樣的代碼在兩種模型中產生了完全不同的行為,這對于開發(fā)者來說顯然是不可接受的(兩個模型都有自己的適用場景,也都有自己的合理性,因此對于模型的好壞不能一概而論)。
那么后來的國際標準組織是如何解決這個沖突的呢?答案就是由開發(fā)者自己選擇。
標準的事件綁定使用addEventListener函數,它接收兩個必傳參數和一個可選參數:必傳的為event(事件名,如"cick")和function(回調函數),可選的為useCapture(是否使用捕獲模型,默認為false,根據MDN的接口說明,這里也可以傳入一個對象,為本次監(jiān)聽設置其他參數,詳細請參考MDN接口文檔 - addEventListener)。
div.addEventListener("click", function(){}, true); //使用捕獲模型
第三個參數就是標識開發(fā)者是否需要使用捕獲模型,默認為false,也就是默認使用微軟的冒泡模型(這是因為大多數事件都只在最內部的元素上觸發(fā),這也間接表明,冒泡模型的普適性更好)。如果開發(fā)者的需求確實需要使用捕獲模型,可以將第三個參數設置為true。比如下面的例子:
事件捕獲與冒泡的用法
了解了事件捕獲與冒泡的基本原理之后,我們舉個例子來說明這兩個模型的基本用法。
假設有以下的DOM結構:
這是兩個重疊的div,當點擊時,兩者都會響應這個click事件。假如事件綁定如下:
var outer = document.querySelector("#outer"); var inner = document.querySelector("#inner"); outer.addEventListener("click", function(e){ alert("來自外部div的消息"); e.stopPropagation(); //阻止事件向內部傳播 }, true); //使用捕獲模型 inner.addEventListener("click", function(e){ alert("來自內部div的消息"); }, true); //使用捕獲模型
頁面上將只顯示外部彈出的消息,內部的事件被e.stopPropagation()攔截了下來,導致事件沒有觸發(fā)。而如果寫成下面的代碼:
var outer = document.querySelector("#outer"); var inner = document.querySelector("#inner"); outer.addEventListener("click", function(e){ alert("來自外部div的消息"); }, false); //使用冒泡模型 inner.addEventListener("click", function(e){ alert("來自內部div的消息"); e.stopPropagation(); //阻止事件向外部傳播 }, false); //使用冒泡模型
這次是只顯示了內部的消息,而沒有顯示外部的消息,說明事件在向上冒泡的過程中被阻止了。
注意
如果是在表格中內嵌復選框,希望實現點擊一行時選中復選框,通過stopPropagation阻止CheckBox響應click事件并不能實現。測試發(fā)現復選框狀態(tài)改變的事件似乎并不是在click事件觸發(fā)的(斷點跟蹤表明,CheckBox在執(zhí)行click回調之前,狀態(tài)就已經發(fā)生了改變,具體是通過什么事件改變了選中狀態(tài)尚不清楚),下面給一個可以處理行點擊的示例:
表格第一行 | |
表格第二行 |
這里沒有使用stopPropagation阻止事件傳播,而是通過為CheckBox定義額外的click事件來解決狀態(tài)不變的問題(經過斷點跟蹤,此時在點擊CheckBox時,狀態(tài)發(fā)生了三次變化,第一次是觸發(fā)了某個原生事件導致其狀態(tài)變化,第二次是執(zhí)行了tr的點擊事件,第三次則是為CheckBox自定義的click事件)。也就是說,點擊tr時狀態(tài)改變一次,點擊CheckBox時狀態(tài)改變三次,功能均正常。
由于在大多數情況下,事件都是由最內層的元素來處理的,所以冒泡模型的應用更為廣泛,它也因此成為綁定事件時使用的默認模型。
總結
事件的捕獲與冒泡兩個模型相對比較簡單,只要明白了其中的原理,就可以很容易掌握通過stopPropagation阻止事件傳播的使用。
瀏覽器的標準事件模型把事件的傳播過程分成了三個階段:捕獲階段、處于目標階段和冒泡階段。捕獲階段指事件從最外層傳播到最內層之前的整個過程,對應捕獲模型;處于目標階段指的是事件剛好傳播到目標元素上;而冒泡階段指的是從最內層元素向外傳播的整個過程。所以我們看到,標準的瀏覽器事件模型就是把捕獲模型和冒泡模型有機地結合起來,使開發(fā)者可以以最簡單的方式靈活地使用兩個模型。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯。