【每日一題NO.87】說(shuō)說(shuō)你對(duì)DOM事件流的理解

事件
JavaScript 和 HTML 之間的交互是通過(guò)事件實(shí)現(xiàn)的。
事件就是通過(guò)文檔或?yàn)g覽器窗口發(fā)生的一些特定的交互瞬間。
可以使用 監(jiān)聽(tīng)器 (或者事件處理程序) 來(lái)預(yù)定事件,以便來(lái)執(zhí)行相應(yīng)的代碼
事件流
描述的是頁(yè)面中事件傳播的順序。
當(dāng)事件發(fā)生的時(shí)候,會(huì)在元素節(jié)點(diǎn)之間按照特定的順序傳播,這個(gè)傳播的過(guò)程叫做 DOM 的事件流。
這個(gè)傳播過(guò)程是:事件捕獲 -- 事件目標(biāo) -- 事件冒泡。
而早期的 IE 和 NetScape 提出了完全相反的事件流概念,IE 是事件冒泡,而 NetScape 的事件流就是事件捕獲。
事件冒泡、事件捕獲
IE 提出的事件流是 事件冒泡,即從下到上,從目標(biāo)觸發(fā)的元素逐級(jí)往下向上傳播,直到 window 對(duì)象。而 NetScape 的事件流就是 事件捕獲,即從 document 對(duì)象逐級(jí)向下傳播到目標(biāo)元素。由于 IE 低版本瀏覽器不支持,所以很少使用事件捕獲。后來(lái) ECMAScript 在 DOM2 中對(duì)事件流進(jìn)行了進(jìn)一步規(guī)范,基本上就是上述二者的結(jié)合,
DOM2 中事件規(guī)定的事件流包括三個(gè)階段:
事件 捕獲 階段:事件從Document節(jié)點(diǎn)上 自上而下 向目標(biāo)節(jié)點(diǎn)傳播的階段 處于目標(biāo)階段:真正的目標(biāo)節(jié)點(diǎn)正在處理事件的階段 事件 冒泡 階段:事件從目標(biāo)節(jié)點(diǎn) 自下而上 向Document傳播的階段

DOM 事件處理
DOM 節(jié)點(diǎn)增加了對(duì)事件處理,其分為四個(gè)級(jí)別
DOM0
DOM0 級(jí)事件具有極好的跨瀏覽器優(yōu)勢(shì),會(huì)以最大的速度綁定
第一種方式是內(nèi)聯(lián)模型【行內(nèi)模式】
<div?onclick="btnClick()">clickdiv>
<script>
??function?btnClick(){
????console.log("hello");
??}
script>
缺點(diǎn)是:不符合 W3C 中關(guān)于內(nèi)容與行為分離的基本規(guī)范
第二種方式是腳本模式
<div?id="btn">點(diǎn)擊div>
<script>
??var?btn=document.getElementById("btn");
??btn.onclick=function(){
????console.log("sunny");
??}
script>
但是會(huì)存在問(wèn)題
<div?id="btn">點(diǎn)擊div>
<script>
??var?btn=document.getElementById("btn");
??btn.onclick=function(){
????console.log("sunny");
??}
??btn.onclick=function(){
????console.log("sunny-sunny");
??}
script>
這個(gè)時(shí)候只能輸出 sunny-sunny。很明顯的是:
第一個(gè)事件函數(shù)被第二個(gè)事件函數(shù)給覆蓋掉了,所以腳本模型的缺點(diǎn)就是:同一個(gè)節(jié)點(diǎn)只能添加一次同類型事件
<div?id="btn3">
????btn3
????<div?id="btn2">
????????btn2
????????<div?id="btn1">
????????????btn1
????????div>
????div>
div>
<script>
????let?btn1?=?document.getElementById("btn1");
????let?btn2?=?document.getElementById("btn2");
????let?btn3?=?document.getElementById("btn3");
????btn1.onclick=function(){
????????console.log(1)
????}
????btn2.onclick=function(){
????????console.log(2)
????}
????btn3.onclick=function(){
????????console.log(3)
????}
script>
當(dāng)點(diǎn)擊 btn1 的時(shí)候,會(huì)依次輸入 1,2,3,這就是事件冒泡,DOM0 級(jí)只支持冒泡階段
DOM2
DOM2定義了兩個(gè)方法:
addEventListener添加事件偵聽(tīng)器removeEventListener刪除事件偵聽(tīng)器
這兩個(gè)方法均有三個(gè)參數(shù):
第一個(gè)參數(shù)是要處理的時(shí)間名稱 第二個(gè)參數(shù)是作為事件處理程序的函數(shù)名稱 第三個(gè)參數(shù)是一個(gè)布爾值,默認(rèn)false表示使用冒泡機(jī)制,true表示捕獲機(jī)制
<div?id="btn">點(diǎn)擊div>
<script>
var?btn=document.getElementById("btn");
btn.addEventListener("click",hello,false);
btn.addEventListener("click",helloagain,false);
function?hello(){
????console.log("hello");
}
function?helloagain(){
????console.log("hello?again");
}
script>
捕獲冒泡的順序
<div?id="btn3">
????????btn3
????????<div?id="btn2">
????????????btn2
????????????<div?id="btn1">
????????????????btn1
????????????div>
????????div>
????div>
????<script>
????????let?btn1?=?document.getElementById("btn1");
????????let?btn2?=?document.getElementById("btn2");
????????let?btn3?=?document.getElementById("btn3");
????????btn1.addEventListener('click',?function?()?{
????????????console.log('btn1捕獲')
????????},?true)
????????btn1.addEventListener('click',?function?()?{
????????????console.log('btn1冒泡')
????????},?false)
????????btn2.addEventListener('click',?function?()?{
????????????console.log('btn2捕獲')
????????},?true)
????????btn2.addEventListener('click',?function?()?{
????????????console.log('btn2冒泡')
????????},?false)
????????btn3.addEventListener('click',?function?()?{
????????????console.log('btn3捕獲')
????????},?true)
????????btn3.addEventListener('click',?function?()?{
????????????console.log('btn3冒泡')
????????},?false)
????script>
點(diǎn)擊btn1,其結(jié)果為:btn3捕獲 btn2捕獲 btn1捕獲 btn1冒泡 btn2冒泡 btn3冒泡
阻止冒泡
btn2.addEventListener(
??"click",
??function?(ev)?{
????ev.stopPropagation();
????console.log("btn2捕獲");
??},
??true
);
所有《每日一題》的 知識(shí)大綱索引腦圖 整理在此:https://www.yuque.com/dfe_evernote/interview/everyday
你也可以點(diǎn)擊文末的 “閱讀原文” 快速跳轉(zhuǎn)

