本文探索一下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…