浅谈:DOM事件模型及事件委托

2018-03-24

前戏

阅读本文需要:

  • 知道有 .addEventListener() 这样一个添加事件监听的函数。

正文

事件模型

思考:假设页面中有这样三个元素:

1
2
3
4
5
<div id="div1">
<div id="div2">
<div id="div3">点我</div>
</div>
</div>

为这三个元素添加点击事件:

1
2
3
div1.addEventListener('click', () => { console.log('div1被点击了') })
div2.addEventListener('click', () => { console.log('div2被点击了') })
div3.addEventListener('click', () => { console.log('div3被点击了') })

那么如果div3被点击,会执行哪个元素的点击事件呢?

这里看起来似乎是div3将他的父元素都覆盖了,所以只会触发div3的事件。然而div3仍然在div1和div2的范围内,从这种角度看应该算作所有的元素都被点击了。而且如果所有元素的点击事件都会被触发,那么事件的执行顺序又是怎样的。

哪种观点来看似乎都没有什么问题,但大家都需要有一个统一的标准,所以DOM就提供了这样的标准,也就是 事件模型

当一个事件发生时,这个事件会在window对象和目标DOM节点之间传播,传播过程中如果遇到DOM节点身上有添加的监听事件就会执行。整个传播过程分为三个阶段:

  1. 捕获阶段:从window对象向目标节点传播。
  2. 目标阶段:在目标节点触发。
  3. 冒泡阶段:从目标节点回到window对象。

DOM节点通过.addEventListener()函数添加事件监听,这个函数除了接收事件类型和执行函数,还可以接收一个参数以指定事件监听的传播阶段,默认值为false也就是冒泡阶段,可以修改为true来使事件在捕获阶段被触发。

那么上文的例子中事件的执行顺序就应该是 3 -> 2 -> 1 。

需要注意的是如果目标元素添加了多个监听事件,那么当它被点击时目标阶段的事件执行顺序是由代码位置决定的,而不取决于事件的捕获与冒泡。

事件委托

如果页面中存在一个元素,这个元素有许多相似的子元素,这些子元素都需要监听自身的点击事件,例如:

1
2
3
4
5
6
<ul id="list">
<li>item1</li>
<li>item2</li>
<li>item3</li>
<li>item4</li>
</ul>

那么为了给这些元素添加监听事件,就需要为每个元素分别添加一个事件监听:

1
2
3
4
5
for (let i = 0; i < list.children.length; i++) {
list.children[i].addEventListener('click', () => {
console.log('列表项被点击了')
})
}

看起来还ok,但实际上这里大部分工作都是无意义的重复动作。如果页面中有100个或者1000个这样的元素,仍然需要分别为每个元素添加事件监听,这就会造成很大的浪费。因为每个响应都是一样的,那么就没必要为这些元素一个一个添加监听,利用事件会在冒泡阶段传播到父元素的特性,可以直接在父元素添加一个监听事件,那么它的子元素每次被点击时,都会通知它自身。这样就能通过这个父元素统一监听和处理这些子元素的事件,这种方法就叫做 事件委托

例如:

1
2
3
4
5
list.addEventListener('click', (event) => {
if (event.target.tagName.toLowerCase() == 'li') {
console.log('列表项被点击了')
}
})

那么事件委托的优点就是提高性能了,毕竟只需要添加一次事件监听。

结语

  • 写结语了吗?
  • 没有哦。