1. 首页

浏览器 DOM 元素的事件代理指的是什么

事件

在网页中,如果想与使用者进行“互动”,必须要通过某种方法知道他都做了什么。当然,浏览器开发者们早已根据 [W3C 事件规范][1]实现好了底层的逻辑,我们只需要通过 Web API 中的 [DOM Event][2],通过注册想监听的 DOM 元素和事件的事件监听器(Event Listener)就可以轻松掌握使用者在网页上的一举一动。

事件监听

我们可以在想要监听事件的 DOM 元素上通过 [addEventListener][3] 注册监听器。例如:

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

document.querySelector('#id').addEventListener('click', clickHandler)

当点击 #id 元素时会触发 clickHandler 并传入一个事件,其内容包含事件传递过程中必要的数据,例如目标元素、当前元素、传递阶段等等。这时我们便可以从中获取所需要的数据,并针对这些数据做你想做的事。

现在的网站有大量的互动,如果通过事件监听一个一个去写,除了效能很差,写起来也很麻烦;这时就体现出“事件代理”的重要性了!

不过在说到事件代理之前,现需要理解 DOM Tree 上的时间传递机制是怎样的

时间传递

可以参考 W3C 所定义的 Event Flow 图:

规范中定义了时间传递的三个阶段:

  • 捕获阶段:由 DOM Tree 的根节点依次向内传递,过程中触发各别元素的捕获阶段事件监听。
  • 目标阶段:到达事件目标(Event Target),[按照注册顺序触发事件监听][4]。
  • 冒泡阶段:由事件目标依序向外传递,过程中触发各别元素的冒泡阶段事件监听。

如图所示,当使用者触发一个DOM 元素的事件时,首先会进入捕获阶段(Capture Phase),从根结点逐步向事件目标传递;到达目标后则进入目标阶段(Target Phase),接着就开始折返,进入向根结点传递的冒泡阶段(Bubbling Phase)

在使用 addEventListener 注册事件监听器时,可以通过传递第三个参数,指定此事件监听要在什么阶段触发:

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

elem.addEventListener('click', eventHandler) // 未指定,预设为冒泡
elem.addEventListener('click', eventHandler, false) // 冒泡
elem.addEventListener('click', eventHandler, true) // 捕获
elem.addEventListener('click', eventHandler, {
  capture: true // 是否为捕获。 IE、Edge 不支持。其他属性请参考 MDN
})

通过简单的来回传递,这样就能更精准的控制触发的时机了!

事件代理

现在终于聊到了事件代理。由于事件传递的机制,子元素的事件在传递过程中势必会经过它的父元素;而事件代理,顾名思义就是将子元素事件监听器交由父元素代理。

什么意思呢?我们直接看个简单的对照例子:

首先是 HTML 骨架:

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

<button id="push">push</button>
<button id="pop">pop</button>

<ul id="list"></ul>

没有事件代理

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

(function() {
  document.querySelector('#push').addEventListener('click', pushHandler)
  document.querySelector('#pop').addEventListener('click', popHandler)

  const list = document.querySelector('#list')

  function pushHandler() {
    list.appendChild(getNewElem(list.childNodes.length))
  }

  function popHandler() {
    document.querySelectorAll('#list>li')[list.childNodes.length - 1].remove()
  }

  function getNewElem(text) {
    const elem = document.createElement('li')
    elem.innerText = text
    elem.addEventListener('click', eventHandler)
    return elem
  }

  function eventHandler(e) {
    alert(e.target.innerText)
  }
})()

有事件代理

 /* Js中文网 - 全球前端挚爱的技术成长平台 https://www.javascriptc.com/ */

(function() {
  document.querySelector('#push').addEventListener('click', pushHandler)
  document.querySelector('#pop').addEventListener('click', popHandler)

  const list = document.querySelector('#list')

  list.addEventListener('click', listClickHandler)

  function pushHandler() {
    list.appendChild(getNewElem(list.childNodes.length))
  }

  function popHandler() {
    document.querySelectorAll('#list>li')[list.childNodes.length - 1].remove()
  }

  function getNewElem(text) {
    const elem = document.createElement('li')
    elem.innerText = text
    return elem
  }

  function listClickHandler(e){
    if (e.target.tagName === 'LI') alert(e.target.innerText)
  }
})()

差异在于事件监听的目标元素

在没有事件代理的版本中每一个 li 上都注册了事件监听器,当数量越来越多时浏览器也就建立了越来越多的监听器,无形中对性能有很大的影响;反之在有事件代理的版本中,将事件监听器注册在了外层的 ul 上,无论内容有多少,浏览器都只需要承担一组事件监听器的消耗。

库和框架中的事件处理

在 DOM 事件处理的这部分,jQuery 和 Vue 都将原生的事件监听器做了封装,方便我们快速设定、使用,甚至会自动帮你移除无用的事件监听。

但是在 React 中,React DOM 上直接注册的事件监听器,其实监听的是 React 额外封装过的 React DOM Event,并将全部事件代理到 document 上,这与原生事件有很大不同;特别是如果混用 React DOM Even tListener 及原生的 addEventListener,事件监听器之间的执行顺序很有可能会和预期不一致,在写 React 的时候要特别注意。

有兴趣深入研究的话可以在[React 源码][5] 中查找关于事件处理的代码部分。


作者:疯狂的技术宅
链接:https://segmentfault.com/a/1190000037794704

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:浏览器 DOM 元素的事件代理指的是什么

链接:https://www.javascriptc.com/4800.html

« 别光知道用console.log调试了,快来试试这些高效的调试方法!
为开源项目做贡献的10个步骤»
Flutter 中文教程资源

相关推荐

QR code