JS 事件冒泡和事件捕獲
- 2019 年 10 月 3 日
- 筆記
寫在前面
W3C規定DOM事件流(event flow )存在三個階段:事件捕獲階段、處於目標階段、事件冒泡階段。dom標準事件流的觸發的先後順序為:先捕獲再冒泡,即當觸發dom事件時,會先進行事件捕獲,捕獲到事件源之後通過事件傳播進行事件冒泡。
對事件冒泡和捕捉的解釋
事件冒泡
在本示例中,當我們點擊孫子div
的時候瀏覽器就會去檢查這個div身上沒有綁定事件,如果有就執行它;然後再檢查它的父元素(兒子div
)身上有沒有綁定事件,如果有就執行;然後再檢查兒子div
的父元素身上有沒有綁定事件,如果有就執行,依此類推,直到html
根元素,這樣由內到外的向外觸發事件就叫做事件冒泡。
let parent = document.querySelector('.parent') let child = document.querySelector('.child') let grandson = document.querySelector('.grandson') parent.addEventListener('click', function(e){ console.log('爸爸') }) child.addEventListener('click', function(e){ console.log('兒子') }) grandson.addEventListener('click', function(e){ console.log('孫子') })
事件捕獲
事件捕獲剛好和事件冒泡相反,事件觸發時是先檢查最外層祖先html
元素沒有綁定事件,如果有則執行它,然後在檢查,然後,它移動到<html>
中單擊元素的下一個祖先元素,並執行相同的操作,然後是單擊元素再下一個祖先元素,依此類推,直到到達實際點擊的元素,就和剝洋蔥一樣。
let parent = document.querySelector('.parent') let child = document.querySelector('.child') let grandson = document.querySelector('.grandson') parent.addEventListener('click', function(e){ console.log('爸爸') }, true) // 注意:這個地方多了一個參數 child.addEventListener('click', function(e){ console.log('兒子') }, true) // 注意:這個地方多了一個參數 grandson.addEventListener('click', function(e){ console.log('孫子') }, true) // 注意:這個地方多了一個參數
addEventListener
在開發中,我們都是通過addEventListener
給元素添加事件處理程式碼,可以同時給同一個元素添加多個事件處理程式程式碼
grandson.addEventListener('click', function(e){ console.log('孫子') }) grandson.addEventListener('click', function(e){ console.log('孫子') }) // 控制台會列印2次孫子
默認情況下,所有事件處理程式都是在冒泡階段註冊的,如果想在捕獲階段註冊一個事件,那麼你可以通過使用addEventListener()
註冊您的處理程式,並將可選的第三個參數useCapture
設置為true,capture
就是捕獲的意思
grandson.addEventListener('click', function(e){ console.log('孫子') }, true) // 第三個參數 useCapture 設置為 true
阻止默認行為
e.preventDefault()
阻止事件傳播
e.stopPropagation()
實戰
點擊別處關閉浮層Demo
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <div class="container"> <button id="btnOpen">打開浮動層</button> <div class="popover" id="popoverlayer"> 這裡是浮動層中顯示的內容 <br> <label ><input type="checkbox" />一個checkbox</label> </div> </div> </body> </html>
body{ border: 5px solid red } .container{ border: 1px solid blue; position: relative; display: inline-block } .popover{ display: none; position: absolute; top: 0; left: 100%; width: 200px; height: 200px; margin-left: 10px; background: #fff; border: 1px solid #000; } .popover::before{ content: ''; position: absolute; top: 5px; left: -20px; border: 10px solid transparent; border-right: 10px solid #000; } .popover::after{ content: ''; position: absolute; top: 5px; left: -19px; border: 10px solid transparent; border-right: 10px solid #fff; } .popover.active{ display: block; }
let container = document.querySelector('.container'); let btnOpen = document.querySelector('#btnOpen'); let popoverlayer = document.querySelector('#popoverlayer'); btnOpen.addEventListener('click', function(e){ console.log('click'); popoverlayer.classList.add('active'); e.stopPropagation(); // 方法2:註冊 document click 只執行一次 // document.addEventListener('click', function(){ // console.log('document once click'); // popoverlayer.classList.remove('active'); // }, {once: true}) }) popoverlayer.addEventListener('click', function(e){ console.log('layer click'); // 阻止傳播,為了避免點擊浮層時不關閉 e.stopPropagation(); }) // 注意這裡監聽的是 document,而不是body // 因為當body的高度不夠的時候,點擊浮層的外部是不會關閉的 // 方法1 document.addEventListener('click', function(){ console.log('document click'); popoverlayer.classList.remove('active'); })
事件委託
使用事件委託實現點擊點擊不同的 li 元素
來獲取歌曲的 id
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width"> <title>JS Bin</title> </head> <body> <ul class="musiclist"> <li data-id="1"><span>歌曲1</span><span>作者1</span></li> <li data-id="2"><span>歌曲2</span><span>作者2</span></li> <li data-id="3"><span>歌曲3</span><span>作者3</span></li> <li data-id="4"><span>歌曲4</span><span>作者4</span></li> <li data-id="5"><span>歌曲5</span><span>作者5</span></li> </ul> </body> </html>
*{ padding: 0; margin:0; box-sizing: border-box; } body{ border: 1px solid #000; padding: 10px; } ul,ol{ list-style: none; } .musiclist{ } .musiclist li{ display: block; padding: 15px 10px; background: #fff; border-bottom: 1px solid #ddd; } .musiclist li:hover{ background: #eee; } .musiclist li>span:first-child{ display: inline-block; font-size: 16px; color: #000; } .musiclist li>span:nth-child(2){ font-size: 10px; color: #000; margin-left: 10px; }
let musiclist = document.querySelector('.musiclist'); // 事件處理程式綁定在歌曲列表上面 musiclist.addEventListener('click', function(e){ console.log('target', e.target); console.log('currentTarget', e.currentTarget); let target = e.target; // 當點擊的元素是 li 時,才能獲取到 dataset 屬性 if(target.nodeName === 'LI'){ console.log('當前歌曲ID是' + target.dataset.id) } })
e.target 和 e.currentTarget 的區別
e.target 是當前觸發事件的元素
e.currentTarget 是事件綁定的元素