理解Event的冒泡模型

weixin_33830216發表於2018-01-16

本文探索一下Event的冒泡過程和初學遇到的幾個小bug

DOM Event概述

Event介面是檢測在DOM中的發生的所有事件,我們一直在用,而且從DOM的很早的版本就一直在用著。早期的網景(後來的火狐)和IE是各自為戰,直到W3C一統江湖,DOM版本一路發展而來,經歷了DOM-0(洪荒時代)、DOM-1(只有兩章核心內容)、DOM-2(劃時代的一個版本,我們學的Event就在這個版本,而且目前的用的也是這個版本)、DOM-3、DOM-4(草案階段)。

  • 通過一個例子喚醒對Event的認識
//1、有一個js函式如下
function print(){
  console.log(1)
}

//2、在html的button裡面點選觸發上面的函式
<button id=button onclick="?">點我</button>
//問號處填可以填什麼 A. print() B.print C.print.call()

//在js裡面的onclick裡面觸發
button.onclick = ?
//問號處可以填什麼 A. print() B.print C.print.call()
複製程式碼
  • 很明顯第一個問號應該選A C,第二個問號應該選B
  • 第一處在HTM中,點選事件要立刻執行程式碼,肯定選擇帶()的,而第二處在JS中,onclick是一個屬性,不需要立刻執行,等使用者點選了,瀏覽器再反應,不需要()

既然onclick等on事件在JS中是一個屬性,那麼後面的就會覆蓋前面的,所以DOM2裡面引入了一個重要的EventListener,是一個佇列。

addEventListener

這是一個佇列,例子1,先進先出的特點,為後面的冒泡模型做準備。

function f(){
  console.log("eventListener不會覆蓋")
}

button2.addEventListener('click', function(){
  console.log("eventListener不會覆蓋1")
})
button2.addEventListener('click', f)
button2.removeEventListener('click', f)
button2.addEventListener('click', function(){
  console.log("eventListener不會覆蓋3")
})
複製程式碼
  • 會列印出什麼呢,答案是eventListener不會覆蓋1 eventListener不會覆蓋3
  • 所以說既然on可以一個列印出結果,就可以藉助remove來實現one執行一次的操作
function f(){
  console.log("eventListener不會覆蓋2")
  button2.removeEventListener('click', f)
}


button2.addEventListener('click', f)

複製程式碼

只會列印一次,不會一直列印了,也就是one的原理。

  • 具體的模型可以看W3C

冒泡模型

上面的官方文件中,我只研究一下捕獲階段(capture phase)和冒泡階段(bubbling phase)。

  • 什麼是冒泡呢?我們先看一段程式碼
grand.addEventListener('click', function(){
  console.log('我是你爺爺')
})
dad.addEventListener('click', function(){
  console.log('我是你爸爸')
})

son.addEventListener('click', function(){
  console.log('我是你兒子')
})
複製程式碼
  • 這是三個div的事件,當你點選的時候,控制檯列印必然會有順序。那麼應該是什麼順序呢,正常人的思維不外乎兩種結果
    • 第一種:我是你的兒子 我是你爸爸 我是你爺爺
    • 第二種: 我是你爺爺 我是你爸爸 我是你兒子
    • 到底是那種呢,W3C說都行,看你程式碼咋寫的了,上面的程式碼列印順序是第一個中,也就是冒泡。

  • 如果你想實現第二種列印方式,也就是捕獲階段,應該修改程式碼如下
grand.addEventListener('click', function(){
  console.log('我是你爺爺')
}, true)
dad.addEventListener('click', function(){
  console.log('我是你爸爸')
}, true)

son.addEventListener('click', function(){
  console.log('我是你兒子')
}, true)
複製程式碼
  • 也就是說addEventListener後面的引數決定了順序,當你不寫的時候是undefined,也就是false的意思。
  • 複習一下五個falsey
    • 0 NaN '' null undefined 除此之外都是true

上圖是簡單的圖解,注意優先執行為true的部分,再執行false的部分。

簡單的例項====================>demo

  • 一個變式
grand.addEventListener('click', function(){
  console.log('我是你爺爺')
}, true)
dad.addEventListener('click', function(){
  console.log('我是你爸爸')
})

son.addEventListener('click', function(){
  console.log('我是你兒子')
複製程式碼
  • 上述程式碼應該是什麼順序呢

  • 誰是true,先列印誰,都是false,繼續按照冒泡順序列印。

一個奇葩的問題

son.addEventListener('click', function(){
  console.log('我是你兒子true')
}, true)

son.addEventListener('click', function(){
  console.log('我是你兒子false')
})
複製程式碼
  • 給同一個元素 false true,應該列印什麼呢
  • 答案是: 按照書寫的順序,誰在前面先列印誰。

意想不到的Bug

parent是關鍵字不能使用,一不小心使用的話會出問題。

  • 你用了關鍵字做變數,把滑鼠點爛也看不到效果。

點選空白,對話方塊消失的案例

  • 領導說有一個需求,點選某個按鈕,彈出對話方塊,點選空白會消失。
  • 你的第一個思路:先把div設為none,點選按鈕的時候,再讓這個div的display是block,點選其他地方變為none。
  • 很好,你去實現一下吧。

第一個bug

  • 很快你會碰到了第一個bug

    • 第一個錯誤:監聽錯了物件

正常來說,應該點選body控制檯列印數字1,你點爛了你的羅技滑鼠也沒出來。為什麼呢?

  • 我們使用border大法,看看它到底在哪

使用了紅色border之後,發現body的高度太矮了,點選不到啊。

  • 你明白監聽錯物件了,那你就換了一個物件,監聽文件唄,肯定沒問題了。

第二個bug

  • 很好,你進入了第二個bug了

    • 第二個bug:你都能點選到,但是彈不出對話方塊了

根據圖片 中的控制檯可以發現,確實都點選到了,監聽沒問題,而且點選後,也是按照冒泡的順序列印的結果。

  • 那為什麼沒有對話方塊了呢

註釋掉出問題的程式碼後,上圖是正常的點選出現對話方塊啊,說明問題就出在註釋的程式碼上。

  • bug出現的原因就在於:預設冒泡的影響,當你點選的浮層那個div,之後,往 body document上冒泡,在document上立刻被殺死,display變為none,你做夢能看到 彈出框啊。

修復第二個bug

我們既然知道了第二個bug產生的原因,那麼我們阻止冒泡順序

  • 解決的方案,不讓其往上冒泡,自己管理。
clickMe.addEventListener('click', function(){
  popover.style.display = 'block'
  console.log('點選浮層了') 
})

wrapper.addEventListener('click', function(e){
  e.stopPropagation()
})


document.addEventListener('click', function(){
  popover.style.display = 'none'
  console.log('點選文件了') 
})
複製程式碼
  • 但是隨之而來的是一個關於記憶體佔用的問題,現在你是隻有一個popover,只有一個函式,等你有了很多個popover,如果按照這個寫法會有很多個函式,所以不能這麼寫,採用下面的寫法,節省記憶體。
$(clickMe).on('click', function(){
  $(popover).show()
  console.log('show')
  setTimeout(function(){
    console.log('one click')
    $(document).one('click', function(){
     console.log('我覺的他不會執行')
     $(popover).hide()            
    })
  },0)
  
})
// $(wrapper).on('click', function(e){
//   e.stopPropagation()
// })

$(document).on('click', function(){
  console.log('走到document啦')
})

複製程式碼
  • 只有點選的時候才用,設定settimeout是為了讓他非同步,不至於立刻隱藏,產生第一個bug。
  • 注意一下,jQuery的 show() hide()

  • 當你點選按鈕,只會列印圖中這兩句話,另外兩句只有再次點選才會列印。

JS版本的節省記憶體的版本==================>節省記憶體

jQuery版本的節省記憶體版本=================>jQuery節省記憶體

對話方塊小三角的製作

.popover{
  display: inline-block;
  border: 1px solid red;
  position: relative;
  padding: 10px;
  margin:10px;
}
.popover::before{
  position: absolute;
  content: '';
  top: 5px;
  right: 100%;
  border: 10px solid transparent;
  border-right-color:red;
}
.popover::after{
  content: '';
  border: 10px solid transparent;
  position: absolute;
  right: 100%;
  top: 5px;
  border-right-color: white;
  margin-right: -1px;
}
複製程式碼

主要利用boder-right-color以及兩個偽元素。

浮層三角的例項=============================>demo

冒泡的直觀體現

點選一下會有驚喜的github.com/codevvvv9/b…

冒個泡

相關文章