在网页中,若是想与使用者进行“互动”,必需要经过某种方法知道他都作了什么。固然,浏览器开发者们早已根据 W3C 事件规范实现好了底层的逻辑,咱们只须要经过 Web API 中的 DOM Event,经过注册想监听的 DOM 元素和事件的事件监听器(Event Listener)就能够轻松掌握使用者在网页上的一举一动。javascript
咱们能够在想要监听事件的 DOM 元素上经过 addEventListener 注册监听器。例如:html
document.querySelector('#id').addEventListener('click', clickHandler)
当点击 #id
元素时会触发 clickHandler
并传入一个事件,其内容包含事件传递过程当中必要的数据,例如目标元素、当前元素、传递阶段等等。这时咱们即可以从中获取所须要的数据,并针对这些数据作你想作的事。前端
如今的网站有大量的互动,若是经过事件监听一个一个去写,除了效能不好,写起来也很麻烦;这时就体现出“事件代理”的重要性了!java
不过在说到事件代理以前,现须要理解 DOM Tree 上的时间传递机制是怎样的react
能够参考 W3C 所定义的 Event Flow 图:git
规范中定义了时间传递的三个阶段:程序员
如图所示,当使用者触发一个DOM 元素的事件时,首先会进入捕获阶段(Capture Phase),从根结点逐步向事件目标传递;到达目标后则进入目标阶段(Target Phase),接着就开始折返,进入向根结点传递的冒泡阶段(Bubbling Phase)。github
在使用 addEventListener
注册事件监听器时,能够经过传递第三个参数,指定此事件监听要在什么阶段触发:面试
elem.addEventListener('click', eventHandler) // 未指定,预设为冒泡 elem.addEventListener('click', eventHandler, false) // 冒泡 elem.addEventListener('click', eventHandler, true) // 捕获 elem.addEventListener('click', eventHandler, { capture: true // 是否为捕获。 IE、Edge 不支持。其余属性请参考 MDN })
经过简单的来回传递,这样就能更精准的控制触发的时机了!segmentfault
如今终于聊到了事件代理。因为事件传递的机制,子元素的事件在传递过程当中势必会通过它的父元素;而事件代理,顾名思义就是将子元素事件监听器交由父元素代理。
什么意思呢?咱们直接看个简单的对照例子:
首先是 HTML 骨架:
<button id="push">push</button> <button id="pop">pop</button> <ul id="list"></ul>
没有事件代理
(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) } })()
有事件代理
(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 源码 中查找关于事件处理的代码部分。