在 JavaScript
中,我们给一个HTML
元素绑定事件响应函数.
在执行事件响应函数的时候,我们可以拿到一个和当前执行事件相关联的事件对象.
这个事件对象包含了此次事件产生的一些关键数据.
其中,最重要的数据之一,就是事件源.
事件对象和事件源
在上一节,阻止事件冒泡中,我们知道了每一次事件执行都会产生了一个 e
的是对象.
在W3C标准模式下,它会以事件响应函数的第一个形参的位置出现.
click me
el.addEventListener('click', function (e) {
console.log(e) // 这里的 e 事件对象.
}, false)
其中事件对象e
身上的属性 srcElement
& target
就表示的是当前事件产生的事件源对象.
这两个属性表达的是同一个HTML
元素.也就是上述的那个el
为什么要有两个同样的属性去表达一个元素的,这里按下暂且不表.
事件源对象到底是哪个?
根据上述情况,我们观察到的现场是:
- 我们给
el
绑定了一个click
事件. - 我们在
el
的点击事件响应函数里,输出了事件对象.并查看了srcElement
和target
-
srcElement
和target
属性都是指向的el
元素.
于是,我们就得出结论了:
事件源对象就是那个[绑定了事件响应函数]并[触发了事件]的那个元素.
于是一个元素是否是事件源的前提是两条:
- [绑定了事件响应函数]
- [触发了事件]
在看看下面这个例子:
长这模样
事件响应函数只给外面的红色DIV
绑定了.
let parent = document.querySelector('.parent')
parent.addEventListener('click',function (e) {
console.log(e)
},false)
按照之前事件源前置条件的推论
- [绑定了事件响应函数]
- [触发了事件]
我们点击里面那个小的黄色的DIV
,如果输出的 srcElement
或者 target
显示的是 外面这个红色的DIV
.
就说明我们的推断是正确的.
查看结果:
发现事件源对象是 div.child
也就是内部的那个黄色的DIV
.
所以,关于谁是事件源的结论,之前就总结错了.
正确的结论是:谁触发了这个事件信号,谁就是事件源,而不管这个元素是否绑定了事件响应函数.
一个基本事实是:
一个元素的事件从元素出生开始就是客观存在的.
它和事件响应函数是两码事.
没有事件响应函数,并不代表这这个元素就不能触发自己的事件..
如果运气好,正好绑定了事件响应函数它就执行,没有它仍然会触发,只不过没有事件响应函数给它执行而已.
所以,事件源,就是触发这个事件的元素,和它绑不绑定事件响应函数没有一毛钱关系.
事件委托模型
事件委托模型的本质就是里用下面两点:
- 事件冒泡模型
- 事件源对象是触发事件的那个元素,和绑定事件响应函数与否无关
考虑一个场景.
一个 ul
下,有 100 个 li
.
每个 li
里都有一个对应的数字.
现在的需求是:
点击某一个 li
就输出里面对应的数字.
- 1
- 2
....
- n
一般做法:
Array.prototype.slice.call(document.getElementsByTagName('li'))
.forEach(li=>{
li.addEventListener('click',function () {
console.log(this.innerText)
}, false)
})
给每一个li
都绑定一个 click
事件响应函数.
然后在事件响应函数的内部输出 innerText
..
逻辑非常顺畅,执行一开始也没大的问题.
但是后续问题来了.
如果现在新添加了一些新的 li
..
我们就不得不重新的给这些新的 li
绑定事件响应函数.
如果li
的数量很多,那么我们就不得不的给每一个li
都绑定这样一个事件响应函数.
利用事件委托模型来做.
document.getElementsByTagName('ul')[0]
.addEventListener('click', function (e) {
console.log(e.target.innerText)
}, false)
- 我们将事件响应函数绑定在父容器
ul
上. - 内部的
li
子元素,虽然没有绑定事件响应函数,但是事件是从它们这里产生的. - 于是每一个点击的
li
子元素就是当前的事件源. - 我们利用
e.target
拿到事件源,就是拿到了当前点击的那个li
. - 非常方便的,就可以在
ul
的事件响应函数里通过e.target.innerText
获取到每一个li
内部文本节点对应的内容了.
这么做的好处:
- 不管有多少个
li
,事件响应函数都只有一个. - 后续即使是添加新的
li
,也无需重新绑定事件响应函数.
但是这么做也有一个小小的前提:
每一个
li
子元素的处理逻辑基本都是一致的.
如果每一个子元素的处理逻辑很复杂,且不一样,数量也不多,那么也没有必要使用这种事件委托模型.
补充
上面留了一个问题:
事件对象
e
身上的属性srcElement
&target
都是当前事件产生的事件源对象.
为什么要两个这样的属性呢?
还是 IE9 以及老板的IE浏览器....
老版本的IE浏览器,事件源对象属性是 srcElement
..
target
则是 W3C 的标准定义事件源属性.
所以,为了满足在IE9以及以下版本的浏览器能够正常执行.
把代码的兼容性写好点,就是下面这种写法.
以事件委托模型为例子
// 添加事件兼容模式
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 的事件响应函数中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)
})