DOM事件模型
事件分发和DOM事件流
众所周知,DOM 是个树形结构,我们在页面上点击一个元素,事件对象将通过 DOM 事件流确定的 DOM 树进行传播。
件对象被调度到事件目标。但是在分发之前,必须首先确认事件对象的传播路径。
在事件传播过程,IE 认为应该先从最小的子元素开始查找事件对象,然后往上传给每一级的父元素,这种由内向外的查找事件对象方式,叫事件冒泡 。
而网景认为应该从最顶层的元素开始查找,逐层往下找,这种由外向内的查找事件对象方式,叫事件捕获。 最后,2002年,W3C 发布标准,规定浏览器应该同时支持两种调用顺序,于是有了如图的事件流。
事件流分为三个阶段:事件捕获阶段、目标阶段、事件冒泡阶段。
目标阶段,活动对象到达事件对象的事件目标。如果事件类型指示事件不会冒泡,则该事件对象将在此阶段完成后停止。
开发者可以自由选择将事件对象放在捕获阶段还是冒泡阶段。
事件绑定
IE 5:element.attachEvent('onclick', fn)// 冒泡
网景:element.addEventListener('onclick', fn)// 捕获
W3C:element.addEventListener('onclick', fn, bool)
如果 bool 不传或为 falsy ,fn 就会走冒泡阶段。即当浏览器进入冒泡阶段发现 element 有 fn 监听函数,就会调用 fn 函数,并提供事件信息。 如果 bool 为 true ,fn 就会走捕获阶段。即当浏览器进入捕获阶段发现 element 有 fn 监听函数,就会调用 fn 函数,并提供事件信息。
当只有一个 div 被监听,fn 分别在捕获阶段和冒泡阶段监听 click 事件
div.addEventListener('click', f1)
div.addEventListener('click', f2, true)
此时,谁先监听就会先执行。
target v.s currentTarget
e.target 是用户操作的元素
e.currentTarget 是程序员监听的元素
取消冒泡
捕获接断不可取消,但是冒泡阶段可以取消。我们通过 e.stopPropagation() 可以中断冒泡,浏览器不再向上走。一般用于封装某些独立的组件。
不可阻止默认动作
有些事件不能阻止默认动作。通过Bubbles和Cancelable来判断。
Bubbles 为该事件是否冒泡,所有冒泡都可取消
Cancelable 为开发者是否可以阻止默认事件
自定义事件
element.addEventListener('click', () => {
const event = new CustomEvent('custom', {
"detail": {name: "custom", age: 18}
})
element.dispatchEvent(event)
})
element.addEventListener('custom', (e) => {
console.log('custom')
console.log(e)
})
DOM事件委托
事件委托,是一种通过事件捕获和事件冒泡实现的事件处理模式。通俗的讲,就是把一个元素的响应事件委托到另一个元素上。
举个例子,如果我们有很多相似方式处理的元素,就没有必要给每个元素分配一个处理事件。这时候,我们就要把元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制触发它的外层元素的绑定事件,然后在外层元素上执行函数。
优点
- 减少内存消耗
假设我们有 100 个元素,如果我们创建 100 个相同的元素监听事件,那么需要创建 100 个内存地址来存储,这样对于内存的消耗是非常大的,需要消耗很多性能。而我们绑定到外层元素,那么只需要创建一个内存地址存储就可以,所以事件委托可以减少大量的内存消耗,节约效率。
- 监听动态元素
此时,如果我们不确定有多少元素需要绑定,我们通过绑定外层元素,通过 e.target 匹配目标元素,就可以实现动态监听了,如果不匹配,可以通过递归向上查找外层元素继续进行匹配,直至匹配成果,或者匹配失败取消函数的执行。
我们可以通过封装一个函数来实现事件委托
function addEvent(element, eventType, selector, fn) {
element.addEventListener(event, (e) => {
const el = e.target
while (!el.matches(selector)) {
if (element === el) {
el = null
break
}
el = el.parentNode
}
el && el.call(el, e, el)
})
}