標籤:

事件捕獲和事件冒泡

事件捕獲和事件冒泡

來自專欄航海日誌

html 頁面和js的互動是通過事件來完成的,事件捕獲是指從document一直到觸發事件的那個節點,而事件冒泡則恰恰於此相反。他們就是事件觸發時序的術語而已,一個從外到內,一個由內到外,不需要大驚小怪,故弄玄虛。下面由幾個經典例子,來說明這個問題。

<div id="father"> <div id="son"> </div></div>

接下來我們添加事件監聽。

document.getElementById(father).addEventListener(click ,function(e){ console.log(father事件被觸發 +this.id); })document.getElementById(son).addEventListener(click,function(e){ console.log(son被觸發+this.id);})

結果就是

son事件被觸發son

father事件被觸發father

這是由內向外的 事件冒泡,接下來看看事件捕獲

document.getElementById(father).addEventListener(click ,function(e){ console.log(father事件被觸發 +this.id); },true)document.getElementById(son).addEventListener(click,function(e){ console.log(son被觸發+this.id);},true)

結果就是

father事件被觸發father

son事件被觸發son

一個有外向內的事件捕獲就發生了,通常我們不寫第三個參數,就是默認為false,也就是事件冒泡。

接下來我們來看一組代碼的對比,加深我們的學習的印象。

我想實現將一個功能,點擊按鈕,就可以彈出一個選擇框,在我點擊非選擇框區域的時候,選擇框就會自動消失。

<div id="wrapper" class="wrapper"> <button id="clickMe">點我</button> <div id="popover" class="popover"> <input type="checkbox">浮層 </div> </div>

我們第一次想到的應該是這樣的

clickMe.addEventListener(click, function(e){ popover.style.display = block})document.addEventListener(click, function(){ popover.style.display = none})

可是當我們點擊之後並沒有發生什麼,為什麼呢,那是因為,在事件冒泡階段,執行完第一句之後,事件繼續向上冒泡,遇到了document的事件,於是乎就執行了,就剛好讓這個浮層又重新變為了『none』,那麼該如何解決呢?

很簡單的思路是,我們讓執行完第一個事件之後,不再冒泡了,事件冒泡就此斷開。

clickMe.addEventListener(click, function(e){ popover.style.display = block})clickMe.addEventListener(click, function(e){ e.stopPropagation() //停止冒泡})document.addEventListener(click, function(){ popover.style.display = none})

接下來我們再看看另外一種思路

$(clickMe).on(click,function(){ $(popover).show() $(document).one(click,fucntion(){ console.log(執行了嗎?) $(popover).hide() })})

可是這仍然沒彈出浮層,這是為什麼呢,我沒有去點擊第二次啊,為什麼會觸發hide()呢?那是因為,我們點擊了按鈕之後,做了兩件事:第一件就是show(),第二件就是為document添加了一個hide()函數,那麼等事件冒泡到了document的時候,發現它有一個很新鮮的,熱乎乎的函數hide(),於是就執行了。

添加了監聽,很快就執行。

有一個解決方案就是,等一等再做這件事情,等我把冒泡階段走完,你再執行。

$(clickMe).on(click,function(){ $(popover).show() console.log(show) setTimeout(function(){ $(document).one(click,fucntion(){ console.log(執行了嗎?) $(popover).hide() }) },0) //即便寫的是0,也是非同步執行,儘可能很快。})//為了方便大家理解,我加了下面一個監聽$(document).on(click,function(){ console.log(click事件走到了document)})

輸出的是

show

click事件走到了document

執行了嗎?

這樣就是我先把整個冒泡走完,再通過非同步,為你添加一個點擊會消失的監聽,可是這時候卻不會執行了,因為冒泡已經結束了,必須我人為的點擊document才會去執行hide()

下面我們來看一個經典題目

<div id="a"> <div id="b"> <div id="c"></div> </div></div>

var a = document.getElementById("a"), b = document.getElementById("b"), c = document.getElementById("c");c.addEventListener("click", function (event) { console.log("c1"); // 注意第三個參數沒有傳進 false , 因為默認傳進來的是 false //,代表冒泡階段調用,個人認為處於目標階段也會調用的});c.addEventListener("click", function (event) { console.log("c2");}, true);b.addEventListener("click", function (event) { console.log("b");}, true);a.addEventListener("click", function (event) { console.log("a1");}, true);a.addEventListener("click", function (event) { console.log("a2")});a.addEventListener("click", function (event) { console.log("a3"); event.stopImmediatePropagation();}, true);a.addEventListener("click", function (event) { console.log("a4");}, true);

  1. 如果點擊c或者b,輸出什麼?
  2. 如果點擊a,輸出什麼?
  3. 如果注釋掉event.stopImmediatePropagation,點擊c,會輸出什麼?

首先來看第一個問題,答案是a1,a3 首先是事件捕獲階段,發現a綁定了輸出a1,a3的函數,那麼執行,也許你會問,為什麼a4不出來呢?那是因為 event.stopImmediatePropagation(),這個函數包括了stopPropagation的功能,也就是阻止冒泡和捕獲,這就是為什麼b,c上面的事件都不會執行,同時也阻止該元素後來綁定的事件處理程序被調用,所以不會輸出a4。

第二個問題是輸出a1,a2,a3,很多人也許會說a1,a3,a2,因為前兩者是在捕獲階段被調用的呀,可是你需要注意的是,目前事件流處於目標階段,非冒泡捕獲,那麼調用順序,就是註冊的順序。

第三個問題的答案是a1,a3,a4,b,c1,c2,a2.你也許會問,為什麼不是c2,c1,那你就去看看上一個問題吧!


推薦閱讀:

前端與AI
AC 2017實錄(二)
Koa源碼分析
聽說你想寫代碼。

TAG:前端開發 |