理解Event的冒泡模型

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

DOM Event概述

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

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

//二、在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,是一個隊列。html

addEventListener

這是一個隊列,例子1,先進先出的特色,爲後面的冒泡模型作準備。java

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的原理。git

  • 具體的模型能夠看W3C

W3C的模型

冒泡模型

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

  • 什麼是冒泡呢?咱們先看一段代碼
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的部分。瀏覽器

簡單的實例====================>demoapp

  • 一個變式
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是關鍵字不能使用,一不當心使用的話會出問題。dom

意外bug

  • 你用了關鍵字作變量,把鼠標點爛也看不到效果。

點擊空白,對話框消失的案例

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

第一個bug

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

    • 第一個錯誤:監聽錯了對象

    監聽錯了對象

正常來講,應該點擊body控制檯打印數字1,你點爛了你的羅技鼠標也沒出來。爲何呢?

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

body的位置

使用了紅色border以後,發現body的高度過矮了,點擊不到啊。

  • 你明白監聽錯對象了,那你就換了一個對象,監聽文檔唄,確定沒問題了。

第二個bug

  • 很好,你進入了第二個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()

jQuery節省內存

  • 當你點擊按鈕,只會打印圖中這兩句話,另外兩句只有再次點擊纔會打印。

具體的分析

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…

冒個泡

相關文章
相關標籤/搜索