JS15 JavaScript中的事件流

當(dāng)點(diǎn)擊頁(yè)面的某個(gè)按鈕時(shí),單擊事件并不僅僅發(fā)生在按鈕上。換句話說,在單機(jī)按鈕的同時(shí),也點(diǎn)擊了按鈕的容器元素,甚至也單機(jī)了整個(gè)頁(yè)面。

==事件流描述的是從頁(yè)面中接受時(shí)間的順序==,IE和Netscape提出了幾乎相反的事件流的概念:

  1. IE的事件流是事件冒泡流
  2. Netscape是事件捕獲流
image

==大多是情況下都是將事件處理程序添加到事件冒泡階段==

事件冒泡

IE的事件流叫做事件冒泡(event bubbling),即事件開始時(shí)由最具體的元素(嵌套層次最深的節(jié)點(diǎn))接受,然后逐級(jí)向上傳播,直至文檔根節(jié)點(diǎn)。

事件冒泡是自內(nèi)而外的去觸發(fā)各個(gè)層次綁定的事件

以下面的HTML頁(yè)面為例:

<!DOCTYPE html>
<html>
  <head>
    <title>Event Bubling Example</title>
  </head>
  <body>
    <div id="myDiv">Click Me</div>
  </body>
</html>

當(dāng)點(diǎn)擊了<div>元素,click事件會(huì)按照如下順序傳播

graph LR
div-->body
body-->html
html-->document

事件從我們單擊的元素開始,沿著DOM樹向上傳播,在每一級(jí)節(jié)點(diǎn)上都會(huì)發(fā)生,直至傳播到document對(duì)象

image

事件捕獲

Netscape提出的另一種事件流是事件捕獲(event capturing),即不太具體的節(jié)點(diǎn)應(yīng)該更早接收到事件,最具體的節(jié)點(diǎn)應(yīng)該最后接收到事件。事件捕獲的用于在于事件到達(dá)預(yù)訂目標(biāo)之前捕獲它。

仍然以上面的頁(yè)面作為例子,在事件捕獲的狀態(tài)下,點(diǎn)擊<div>元素,click事件會(huì)按照相反的順序傳播:

graph LR
document-->html
html-->body
body-->div

事件捕獲過程中,document對(duì)象最先接收到click事件,然后事件沿DOM樹向下,直至傳播到事件的實(shí)際目標(biāo),即<div>元素

image

DOM事件流

==DOM2級(jí)事件規(guī)定的事件流包括了三個(gè)階段==:

  1. 事件捕獲階段
  2. 處于目標(biāo)階段
  3. 事件冒泡階段

首先發(fā)生的事件捕獲,為截獲事件提供了機(jī)會(huì)。然后是實(shí)際的目標(biāo)接收到事件,最后一個(gè)階段是冒泡階段,可以在這個(gè)階段對(duì)事件作出相應(yīng)。

以前面的HTML頁(yè)面為例,單機(jī)<div>元素會(huì)按照下圖順序觸發(fā)事件

image

在DOM事件流中,實(shí)際的目標(biāo)<div>元素在捕獲階段不會(huì)接收到事件,這意味著在捕獲階段事件從documenthtml再到body后就停止了。下一個(gè)階段是“處于目標(biāo)”階段,事件在<div>上發(fā)生。然后冒泡階段發(fā)生,事件有傳播回document

==在事件處理中,事件處于目標(biāo)階段被看成冒泡階段的一部分。==

==為了最大限度的兼容,大多是情況下都是將事件處理程序添加到事件冒泡階段。不是特別需要,不建議在事件捕獲階段注冊(cè)事件處理程序==

DOM2級(jí)事件處理程序

DOM2級(jí)事件定義了對(duì)兩個(gè)方法:

  1. addEventListener:添加事件處理程序
  2. removeEventListener:刪除事件處理程序

所有DOM節(jié)點(diǎn)都包括這兩個(gè)方法,它們都接受3個(gè)參數(shù),前兩個(gè)參數(shù)是要處理的事件名和作為事件處理程序的函數(shù)。

第三個(gè)參數(shù)是一個(gè)布爾值,如果是true,表示在捕獲階段調(diào)用事件處理程序,如果是false,表示在冒泡階段調(diào)用事件處理程序。默認(rèn)為false

看一個(gè)例子:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>事件冒泡</title>
  <style>
    #outer {
      width: 500px;
      height: 300px;
      background: darkcyan;
    }
    #inner {
      width: 300px;
      height: 150px;
      background: chocolate;
    }
  </style>
</head>
<body>
  <div id="outer">Parent
    <div id="inner">Child</div>
  </div>
</body>
<script>
  const outer = document.getElementById('outer'),
    inner = document.getElementById('inner');
  outer.addEventListener('click', function () {
    console.log('parent')
  });
  inner.addEventListener('click', function () {
    console.log('child')
  });
</script>
</html>

image

而點(diǎn)擊child,事件在冒泡階段觸發(fā),首先被觸發(fā)的是child本身,打印了child,然后向上層冒泡,觸發(fā)parent的事件,打印出parent

addEventListener的第三個(gè)參數(shù)改變?yōu)?code>true,則事件觸發(fā)由冒泡階段轉(zhuǎn)為捕獲階段,此時(shí),當(dāng)點(diǎn)擊child,事件在捕獲階段的傳播從document開始,向下傳播到parent,然后再到child本身,所以會(huì)先打印parent,然后才打印出child

當(dāng)冒泡階段和捕獲階段都會(huì)調(diào)用事件處理程序時(shí),事件按DOM事件流的順序執(zhí)行時(shí)間處理程序

image

當(dāng)事件處于目標(biāo)階段時(shí),事件調(diào)用順序決定于綁定事件的書寫順序。

outer.addEventListener('click', function() {
  console.log('parent冒泡')
}, false);
outer.addEventListener('click', function() {
  console.log('parent捕獲')
}, true);
inner.addEventListener('click', function() {
  console.log('child冒泡')
}, false);
inner.addEventListener('click', function() {
  console.log('child捕獲')
}, true);

上面的代碼,由于child是處于目標(biāo)階段的事件,執(zhí)行的順序和我們書寫的順序有關(guān),因此是'child冒泡'在前,而'child捕獲'在后。而parent的事件就會(huì)按照DOM事件流順序執(zhí)行,即先'parent捕獲''parent冒泡'

因此,這種情況下的調(diào)用順序是:

graph LR
parent捕獲-->child冒泡
child冒泡-->child捕獲
child捕獲-->parent冒泡

如果我們改動(dòng)一下書寫順序:

outer.addEventListener('click', function() {
  console.log('parent冒泡')
}, false);
outer.addEventListener('click', function() {
  console.log('parent捕獲')
}, true);
inner.addEventListener('click', function() {
  console.log('child捕獲')
}, true);
inner.addEventListener('click', function() {
  console.log('child冒泡')
}, false);

那么調(diào)用順序就變成了:

graph LR
parent捕獲-->child捕獲
child捕獲-->child冒泡
child冒泡-->parent冒泡

事件委托

事件委托就是把一個(gè)元素響應(yīng)事件的函數(shù)委托到父層或者更外層元素上,真正綁定事件的是外層元素,當(dāng)事件響應(yīng)到需要綁定的元素上時(shí),會(huì)通過事件冒泡機(jī)制從而觸發(fā)它的外層元素的綁定事件上,然后在外層元素上去執(zhí)行函數(shù)。

事件委托的優(yōu)點(diǎn):

  1. 提高性能
  2. 動(dòng)態(tài)綁定事件(不必再為動(dòng)態(tài)生成的、需要綁定事件的元素逐一綁定事件)

這樣的一個(gè)例子:鼠標(biāo)放到li上,讓對(duì)應(yīng)的li背景變紅:

<body>
  <ul id="ul">
    <li>item1</li>
    <li>item2</li>
    <li>item3</li>
  </ul>
</body>
<script>
  document.querySelector('#ul').addEventListener('mouseover', function (e) {
    if(e.target.tagName.toLowerCase() === 'li') {
      document.querySelectorAll('#ul li').forEach(v => {
        if(v.tagName.toLowerCase() === 'li') {
          v.style.backgroundColor = '#fff'
        }
      });
      e.target.style.backgroundColor = 'red'
    }
  });
  // 上面的forEach遍歷也可以通過再添加一個(gè)事件處理程序完成
  // document.querySelector('#ul').addEventListener('mouseout', function (e) {
  //   if(e.target.tagName.toLowerCase() === 'li') {
  //     e.target.style.backgroundColor = '#fff'
  //   }
  // })
</script>
</html>

target屬性可返回事件的目標(biāo)節(jié)點(diǎn)(觸發(fā)該事件的節(jié)點(diǎn)),如生成事件的元素、文檔或窗口。

這樣避免了在所有的li上綁定事件,而且如果是動(dòng)態(tài)生成的節(jié)點(diǎn),直接在li上綁定事件就需要再次綁定,而事件委托就不需要。

利用jquery也可以實(shí)現(xiàn):

$("ul").on("click","li",function(){
  // some code here
})

其中on方法的第二個(gè)參數(shù)是一個(gè)jQuery選擇器,用于指定哪些后代元素可以觸發(fā)綁定的事件。如果該參數(shù)為null或被省略,則表示當(dāng)前元素自身綁定事件(實(shí)際觸發(fā)者也可能是后代元素,只要事件流能到達(dá)當(dāng)前元素即可)。

阻止事件冒泡

event.stopPropagation()

可以使用event.stopPropagation()來阻止事件繼續(xù)傳播,在上面的parentchild的例子中,如果將child綁定的事件改為下面的代碼:

outer.addEventListener('click', function() {
  console.log('parent冒泡')
}, false);
inner.addEventListener('click', function() {
  console.log('child冒泡');
  e.stopPropagation()
}, false);

這樣再次點(diǎn)擊child,就不會(huì)觸發(fā)parentclick事件

e.target

也可以通過e.target來阻止事件冒泡

document.body.addEventListener('click', e = > {
  // 通過e.target判斷阻止冒泡
  if (e.target && e.target.matches('a')) {
    return;
  }
})

return false

JS的原生的事件綁定中,return false是不能阻止事件冒泡的,它可以用來阻止瀏覽器的默認(rèn)事件,作用于event.preventDefault()相同

在jQuery綁定的事件中使用return false,既阻止默認(rèn)行為又停止冒泡

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容