在 JavaScript
中,我們給一個HTML
元素綁定事件響應(yīng)函數(shù).
在執(zhí)行事件響應(yīng)函數(shù)的時候,我們可以拿到一個和當(dāng)前執(zhí)行事件相關(guān)聯(lián)的事件對象.
這個事件對象包含了此次事件產(chǎn)生的一些關(guān)鍵數(shù)據(jù).
其中,最重要的數(shù)據(jù)之一,就是事件源.
事件對象和事件源
在上一節(jié),阻止事件冒泡中,我們知道了每一次事件執(zhí)行都會產(chǎn)生了一個 e
的是對象.
在W3C標(biāo)準(zhǔn)模式下,它會以事件響應(yīng)函數(shù)的第一個形參的位置出現(xiàn).
<div class='el'>click me</div>
el.addEventListener('click', function (e) {
console.log(e) // 這里的 e 事件對象.
}, false)
其中事件對象e
身上的屬性 srcElement
& target
就表示的是當(dāng)前事件產(chǎn)生的事件源對象.
這兩個屬性表達(dá)的是同一個HTML
元素.也就是上述的那個el
為什么要有兩個同樣的屬性去表達(dá)一個元素的,這里按下暫且不表.
事件源對象到底是哪個?
根據(jù)上述情況,我們觀察到的現(xiàn)場是:
- 我們給
el
綁定了一個click
事件. - 我們在
el
的點(diǎn)擊事件響應(yīng)函數(shù)里,輸出了事件對象.并查看了srcElement
和target
-
srcElement
和target
屬性都是指向的el
元素.
于是,我們就得出結(jié)論了:
事件源對象就是那個[綁定了事件響應(yīng)函數(shù)]并[觸發(fā)了事件]的那個元素.
于是一個元素是否是事件源的前提是兩條:
- [綁定了事件響應(yīng)函數(shù)]
- [觸發(fā)了事件]
在看看下面這個例子:
<div class="parent">
<div class="child"></div>
</div>
長這模樣
事件響應(yīng)函數(shù)只給外面的紅色DIV
綁定了.
let parent = document.querySelector('.parent')
parent.addEventListener('click',function (e) {
console.log(e)
},false)
按照之前事件源前置條件的推論
- [綁定了事件響應(yīng)函數(shù)]
- [觸發(fā)了事件]
我們點(diǎn)擊里面那個小的黃色的DIV
,如果輸出的 srcElement
或者 target
顯示的是 外面這個紅色的DIV
.
就說明我們的推斷是正確的.
查看結(jié)果:
發(fā)現(xiàn)事件源對象是 div.child
也就是內(nèi)部的那個黃色的DIV
.
所以,關(guān)于誰是事件源的結(jié)論,之前就總結(jié)錯了.
正確的結(jié)論是:誰觸發(fā)了這個事件信號,誰就是事件源,而不管這個元素是否綁定了事件響應(yīng)函數(shù).
一個基本事實(shí)是:
一個元素的事件從元素出生開始就是客觀存在的.
它和事件響應(yīng)函數(shù)是兩碼事.
沒有事件響應(yīng)函數(shù),并不代表這這個元素就不能觸發(fā)自己的事件..
如果運(yùn)氣好,正好綁定了事件響應(yīng)函數(shù)它就執(zhí)行,沒有它仍然會觸發(fā),只不過沒有事件響應(yīng)函數(shù)給它執(zhí)行而已.
所以,事件源,就是觸發(fā)這個事件的元素,和它綁不綁定事件響應(yīng)函數(shù)沒有一毛錢關(guān)系.
事件委托模型
事件委托模型的本質(zhì)就是里用下面兩點(diǎn):
- 事件冒泡模型
- 事件源對象是觸發(fā)事件的那個元素,和綁定事件響應(yīng)函數(shù)與否無關(guān)
考慮一個場景.
一個 ul
下,有 100 個 li
.
每個 li
里都有一個對應(yīng)的數(shù)字.
現(xiàn)在的需求是:
點(diǎn)擊某一個 li
就輸出里面對應(yīng)的數(shù)字.
<ul>
<li>1</li>
<li>2</li>
....
<li>n</li>
<ul>
一般做法:
Array.prototype.slice.call(document.getElementsByTagName('li'))
.forEach(li=>{
li.addEventListener('click',function () {
console.log(this.innerText)
}, false)
})
給每一個li
都綁定一個 click
事件響應(yīng)函數(shù).
然后在事件響應(yīng)函數(shù)的內(nèi)部輸出 innerText
..
邏輯非常順暢,執(zhí)行一開始也沒大的問題.
但是后續(xù)問題來了.
如果現(xiàn)在新添加了一些新的 li
..
我們就不得不重新的給這些新的 li
綁定事件響應(yīng)函數(shù).
如果li
的數(shù)量很多,那么我們就不得不的給每一個li
都綁定這樣一個事件響應(yīng)函數(shù).
利用事件委托模型來做.
document.getElementsByTagName('ul')[0]
.addEventListener('click', function (e) {
console.log(e.target.innerText)
}, false)
- 我們將事件響應(yīng)函數(shù)綁定在父容器
ul
上. - 內(nèi)部的
li
子元素,雖然沒有綁定事件響應(yīng)函數(shù),但是事件是從它們這里產(chǎn)生的. - 于是每一個點(diǎn)擊的
li
子元素就是當(dāng)前的事件源. - 我們利用
e.target
拿到事件源,就是拿到了當(dāng)前點(diǎn)擊的那個li
. - 非常方便的,就可以在
ul
的事件響應(yīng)函數(shù)里通過e.target.innerText
獲取到每一個li
內(nèi)部文本節(jié)點(diǎn)對應(yīng)的內(nèi)容了.
這么做的好處:
- 不管有多少個
li
,事件響應(yīng)函數(shù)都只有一個. - 后續(xù)即使是添加新的
li
,也無需重新綁定事件響應(yīng)函數(shù).
但是這么做也有一個小小的前提:
每一個
li
子元素的處理邏輯基本都是一致的.
如果每一個子元素的處理邏輯很復(fù)雜,且不一樣,數(shù)量也不多,那么也沒有必要使用這種事件委托模型.
補(bǔ)充
上面留了一個問題:
事件對象
e
身上的屬性srcElement
&target
都是當(dāng)前事件產(chǎn)生的事件源對象.
為什么要兩個這樣的屬性呢?
還是 IE9 以及老板的IE瀏覽器....
老版本的IE瀏覽器,事件源對象屬性是 srcElement
..
target
則是 W3C 的標(biāo)準(zhǔn)定義事件源屬性.
所以,為了滿足在IE9以及以下版本的瀏覽器能夠正常執(zhí)行.
把代碼的兼容性寫好點(diǎn),就是下面這種寫法.
以事件委托模型為例子
// 添加事件兼容模式
function addEvent(el, type, fn) {
if (el.addEventListener) {
el.addEventListener(type, fn)
} else if (el.attchEvent) {
el.attchEvent('on' + type, function () {
fn.call(el) // 解決 <=IE9 版本以下的瀏覽器 attachEvent 的事件響應(yīng)函數(shù)中this指向的不是el元素的問題.
})
} else {
el['on' + type] = fn
}
}
let ul = document.getElementsByTagName('ul')
addEvent(ul,'click', function (e) {
let event = e || window.event // 獲取事件對象兼容模式
let target = event.target || event.srcElement // 獲取事件源對象兼容模式.
console.log(target.innerText)
})