JavaScript 事件代理简单例子

“代理”能够理解为代劳或者帮忙,或者相似你的事就是个人事这种乐于助人的良好品质。JavaScript 也有许多事件要处理,接着就演示下,如何在 DOM 元素中选出一位热心肠的好同窗。javascript

场景

想象一个平平无奇的表格,像下面这样:html

需求是点击删除按钮可删除单条数据。java

用 JavaScript 实现点击删除的操做,须要监听按钮点击事件,在事件点击回调中,发送删除数据的请求,请求成功后从新渲染表格。函数

基本实现

基本款的实现大概以下:性能

HTML:ui

...
<tr>
  <td>1</td>
  <td>汉堡</td>
  <td>
    <button data-id="1">删除</button>
  </td>
</tr>
...
复制代码

JavaScript:this

const deleteItem = (id) => {
  // 依据 ID 删除数据。
}

let btnList = document.getElementsByClassName('btn')
Array.prototype.forEach.call(btnList, (btn) => {
  btn.addEventListener('click', function (e) {
    deleteItem(this.dataset.id)
  })
})
复制代码

上面的代码给每一条删除按钮都添加了点击事件的回调。上面的代码利用自定义属性 data-id 将单条数据的 ID 传给数据的删除按钮,而后在点击事件的回调中经过 dataset 属性取得,将其传递给处理删除的函数。spa

这样能够实现上面的上面所说的删除功能。prototype

反作用

但是这么写,当数据一多,将会对页面性能形成影响。设计

参考《JavaScript 高级程序设计》:

首先,每一个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事先指定全部事件处理程序而致使的 DOM 访问次数,会延迟整个页面的交互就绪时间。

面对这种情况,解决方案是事件代理。

事件代理实现

先看代码:

let table = document.getElementById('table')
table.addEventListener('click', function (e) {
  if (e.target.tagName === 'BUTTON') {
    deleteItem(e.target.dataset.id)
  }
})
复制代码

上面的代码,首先在回调中经过事件对象 etarget 属性取得触发点击事件的元素,而后经过 tagName 判断这个元素是否是按钮,若是是的话,就进行删除操做。

只添加了一次事件处理函数,就实现了功能。

这里利用的是事件流的事件冒泡。

原理

事件流

事件流有三个阶段,分别是事件捕获阶段,处于目标阶段,还有事件冒泡阶段。

按钮点击事件的事件流以下:

上图省略了 tr ,td, tbody 这些元素,只用关键的元素来展现事件流。

“事件捕获”是不太精确的目标先接收到事件,首先是文档,而后再到 body,逐渐精确到最具体的节点 button.

button 接收到事件,即为“处于目标阶段”。

以后,事件再向外层逐级传播到不太具体的节点,这即是“事件冒泡阶段”。

冒泡与捕获演示

假若有个外层元素称为 box, 它里面包含了一个按钮 btn

boxbtn 注册点击事件,代码以下:

box.addEventListener('click', function (e) {
  console.log('box')
})
btn.addEventListener('click', function (e) {
  console.log('button')
})
复制代码

点击 btn

能够看到,首先打印的是 'button',接着才是 box. 由于 addEventListener 默认是在冒泡阶段执行回调函数的。这里,最具体的 btn 接受到事件以后,向外层冒泡,box 才能接受到事件。

改一下代码:

box.addEventListener('click', function (e) {
  console.log('box')
}, true)
btn.addEventListener('click', function (e) {
  console.log('button')
})
复制代码

这里给 boxaddEventListener 传入了第三个参数 true, 表示要在捕获阶段处理 box 的点击事件。

addEventListener 第三个参数形参为 useCapture, 表示是否在捕获阶段处理事件,默认为 false.

运行一下:

这样一改,首先打印的就是 'box', 由于事件具体节点是 btn,而事件要从不太具体的节点,通过捕获阶段,才肯定到 btn. 在捕获阶段,外层 box 要比 btn 早接受到事件,因此先执行了 box 捕获阶段的事件处理函数。

总结

事件代理的代码中,注册点击事件的是 table 元素,发生点击事件最具体的节点是 button. 事件在 button 发生后,因为冒泡,会传播到 table, 从而触发 table 上事先注册的回调函数。因此,用事件冒泡实现事件代理,能够只给热心肠同窗 table 添加点击事件回调,而没必要劳烦表格中的每一个 button .

在相似的场景下,可利用事件代理,来减小冗余,节约资源。

相关文章
相关标签/搜索