DOM 事件機制&事件委託
一、事件機制
事件是在程式設計時系統內發生的動作或者發生的事情,系統會在事件出現的時候觸發某種訊號並且會提供一個自動載入某種動作的機制(來自MDN)。
每個事件都有事件處理器(有時也叫事件監聽器),也就是觸發事件時執行的程式碼塊。嚴格來說事件監聽器監聽事件是否發生,然後事件處理器對事件做出反應。
二、DOM事件流
事件傳播是一種機制,用於定義事件如何傳播或通過DOM樹傳播,事件傳播有兩種方式:事件捕獲(Capture)和事件冒泡(Bubble)。
事件傳播形式上有三個階段:
- 捕獲階段:從視窗進入事件目標階段
- 目標階段: 目標階段
- 冒泡階段:從事件目標回到視窗
但是,目標極端在現代瀏覽器中沒有單獨處理,所以當一個事件發生在具有父元素的元素上時,現代瀏覽器執行兩個不同的階段 - 捕獲階段和冒泡階段。
三、事件捕獲
事件發生時,在捕獲階段,事件從視窗向下通過DOM樹傳播到目標節點,即從最外層元素(祖先元素)觸發事件響應函式,逐級往下,直到目標元素。(從外到內)
如果目標元素的任何祖先(即父、祖父等)和目標本身具有針對該型別事件專門註冊的捕獲事件偵聽器,則這些偵聽器將在捕獲階段執行。
四、事件冒泡
在事件冒泡階段,正好相反。
事件冒泡模式流程:事件發生時,先觸發目標元素(最直接元素)的事件響應函式,然後觸發其父元素的事件響應函式,並逐級上溯到祖先元素。(從內到外)
五、W3C事件模型
因為有捕獲和冒泡兩種傳播方式,W3C制定了一個標準可以讓我們自己選擇使用哪種傳播方式addEventListener('click',fn,?)
第三個引數
?
是一個bool
值,決定使用捕獲或者冒泡。當你
addEventListener
函式第三個引數為true
時就表示你使用的是事件捕獲。父級元素先觸發,子級元素後觸發。當你
addEventListener
函式第三個引數為空或為false
時就表示你使用的是事件冒泡。子級元素先觸發,父級元素後觸發。六、target vs currentTarget
e.target 使用者操作的元素
e.currentTarget 程式設計師監聽的元素
this是e.currentTarget,不推薦使用
例:
div>span{文字},使用者點選文字
e.target就是span
this是e.currentTarget就是div
七、阻止事件傳播
在巢狀的元素中,並且每個元素都有事件處理程式時,當單擊內部元素,所有處理程式都將同時執行,因為事件會出現在DOM樹中。
為了防止這種情況,可以使用**event.stopPropagation()**
方法停止事件冒泡。
<div id="div1" style="border: 1px solid red; width: 100px; height: 100px;">
<div id="div2" style="border: 1px solid blue; width: 50px; height: 50px;"></div>
</div>
<script>
hi.addEventListener("click", function(){
console.log('div1')
});
hello.addEventListener("click", function(e){
console.log('div2')
e.stopPropagation()
});
</script>
因為在子元素點選事件中使用了event.stopPropagation()
阻止冒泡事件,所以最終只打印出了目標元素'div2'
,父元素的'div1'
並沒有被打印出。
八、阻止預設事件
有些事件具有與之關聯的預設操作。例如點選一個連結瀏覽器帶你到連結的目標,點選一個表單提交按鈕瀏覽器提交表單等等。
可以使用事件物件的preventDefault()
方法來防止此類預設操作。但是,阻止預設操作並不會停止事件傳播,事件像往常一樣繼續傳播到DOM樹。
<a id='div1' href='https://baidu.com'>點選跳轉</a>
<script>
a.addEventListener("click", function(e){
e.preventDefault();
});
</script>
我們給a
新增點選事件,當用戶點選點選跳轉
就阻止a
標籤的預設事件,所以點選後不會有跳轉。
九、是否可以阻止冒泡
並不是所有事件都可以阻止冒泡的,具體可以MDN
搜尋scroll event
,看到Bubbles
和Cancelable
Bubbles
的意思是該事件是否冒泡Cancelable
的意思是開發者是否可以阻止冒泡
event.target & event.currentTarget
e.target
指向事件觸發的元素e.currentTarget
指向事件繫結的元素
十、事件委託
事件委託就是利用事件冒泡,只指定一個事件處理程式,就可以管理某一型別的所有事件。
事件委託:不監聽元素 C 自身,而是監聽其祖先元素 P,然後判斷 e.target 是不是該元素 C(或該元素的子元素)
阻止預設動作:e.preventDefault() 或者 return false
阻止冒泡:e.stopPropagation()
優點:
省監聽數,減少記憶體消耗
<div id="div1">
<button>click 1</button>
<button>click 2</button>
<button>click 3</button>
<button>click 4</button>
<button>click 5</button>
</div>
<script>
div1.addEventListener('click', (e)=> {
//把目標元素賦給t
const t = e.target
// 判斷是否匹配目標元素
if (t.tagName.toLowerCase() === 'button') {
console.log('button內容是:' + t.textContent);
}
});
</script>
可以監聽動態元素(不存在的元素)
<div id="div1">
</div>
<script>
setTimeout(()=>{
//div1裡面新增一個button
const button = document.creatElement('button')
button.textContent = 'click 1'
div1.appendChild(button)
},1000)
div1.addEventListener('click',(e)=>{
const t=e.target
if (t.tagName.toLowerCase() ==='button'){
console.log('button被click')
}
});
</script>
封裝事件委託
<div id="div1">
</div>
<script>
setTimeout(()=>{
const button = document.creatElement('button')
button.textContent = 'click 1'
div1.appendChild(button)
},1000)
on('click','#div1','button',()=>{
console.log('button被點選了')
})
function on(eventType, element, selector, fn){
//判斷如果element不是元素
if(!(element instanceof Element)){
element = document.querySelector(element)
}
element.addEventListener(eventType,(e)=>{
const t = e.target
//matches判斷一個元素是否滿足一個選擇器
if(t.matches(selector)){
fn(e)
}
})
}
</script>