•••• 写在前面 ••••
在我之前的一篇文章《前端开发中的Error以及异常捕获》中,我提到【重写了 addEventListener 方法过后, React无法正常工作,且无任何报错】的问题。经过5个小时左右的折腾,我终于找到了原因,这里就不细说了,大概就是我重写了 addEventListener 而忽略了 removeEventListener ,添加的事件无法被正确的移除了,导致了这个问题。趁着这个机会,来总结一下Web中的事件系统。
•••• Web中的事件 ••••
事件并不是 JavaScript的核心部分,他们是在浏览器的 WebApi中定义的。下面列举的几种情况,都属于发生了事件。
- 用户在某个元素上点击鼠标或悬停光标。
- 用户在键盘中按下某个按键。
- 用户调整浏览器的大小或者关闭浏览器窗口。
- 一个网页停止加载。
- 提交表单。
- 播放、暂停、关闭视频。
- 发生错误。
我们可以在代码中使用 事件处理器来处理各种事件。
•••• 事件模型 ••••
假设我们有这么一段 html代码:
<html> <body> <table> <tbody> <tr> <td>Shady Grove</td> <td>Aeolain</td> </tr> <tr> <td>Over the River, Charile</td> <td>Dorian</td> </tr> </tbody> </table> </body> </html>
如果我们点击 over theRive,Charile,整个事件流程如下:
事件会经历三个阶段,上图中分别由红色、蓝色、绿色标出。第一阶段为红色,事件流从根元素一直走到点击的目标元素,这个过程称为 捕获。第二阶段为蓝色。这个阶段中,会处理点击事件,为事件加上各种属性等。第三阶段为绿色,事件又回重新回到根元素,这个过程称为 冒泡。在整个事件流中,我们在事件流经过的任何元素上,都能监听到该事件,从而进行处理。
一般建议在冒泡阶段处理事件,这样可以最大限度的兼容各种浏览器。
注意:blur、focus、load、unload 等几个事件不会冒泡。
原因是在于:这些事件仅发生于自身上,而它的任何父节点上的事件都不会产生,所有不会冒泡。
我们可以查看事件的bubbles属性,来判断该事件是否可以冒泡。
•••• 事件处理 ••••
EventTarget
EventTarget是一个由可以接收事件的对象实现的接口,并且可以为它们创建侦听器。Web中的所有事件处理器都是由 EventTarget"提供"的。
addEventListener
该方法将指定的监听器注册到 EventTarget 上,当该对象触发指定的事件时,指定的回调函数就会被执行。 事件目标可以是一个文档上的元素 Element, Document和 Window或者任何其他支持事件的对象 (比如 XMLHttpRequest)。其标准语法如下:
1. target.addEventListener(type, listener[, options]); 2. target.addEventListener(type, listener[, useCapture]);
- type: 字符串。表示事件类型,比如: click。
- listener:函数。事件触发时的回调函数。这个函数会接受一个
Event事件对象。这个Event事件对象中,包含了以下重要的属性和方法(这里只列举出常用的)
options :对象。指定一个 listener的配置参数。
- useCapture:布尔值,可选。默认为
false,事件在冒泡过程中触发listener。
removeEventListener
删除使用 addEventListener注册到 target上的事件。标准语法:
target.removeEventListener(type, listener[, options]); target.removeEventListener(type, listener[, useCapture]);
为了提高页面性能,我们在处理完某事件,并且不用继续监听该事件时,可以将之前注册的事件监听函数移除。需要注意的是,如果注册事件时,在捕获或冒泡阶段均进行了监听,那么移除时需要分别移除。
currentTarget 与 target的区别
在事件处理函数中,我们经常会使用到事件的这两个属性。 currentTarget表示注册事监听的对象。 target表示事件起源的对象。举个例子:
1. <div id="father"> 2. <div id="child1">child1</div> 3. <div id="child2">child2</div> 4. <div id="child3">child3</div> 5. </div> 6. 7. document.getElementById('father').addeventListener('click', function(e){ 8. console.log(e.currentTarget); 9. console.log(e.target); 10. });
我们将事件处理函数绑定在 father上。现在,如果我们点击的是 child1,由于 child1是事件源,那么 e.target就是 child1。而我们的事件处理函数是绑定在 father上的,所以, e.currentTarget就是 father。这一点在开发过程中需要特别注意。
•••• 事件代理 ••••
这个概念依赖于这样一个事实,如果你想要在大量子元素中单击任何一个都可以运行一段代码,您可以将事件监听器设置在其父节点上,并将事件监听器气泡的影响设置为每个子节点,而不是每个子节点单独设置事件监听器。举个栗子:
<ul> <li>1<li> <li>2<li> <li>3<li> <li>4<li> <li>5<li> <li>6<li> <li>7<li> <li>8<li> <li>9<li> </ul>
我们要实现点击每一个 li的时候,输出 li内对应的数字,我们当然可以直接在所有的 li上分别绑定一个事件。但是这样会造成内存的浪费。我们可以只在 ul上绑定一个事件,根据事件的 target来获取当前点击的 li,拿到该 li内对应的数字。
document.querySelector('ul').addEventListener('click', function(e){ if(e.target && e.target.nodeName === "LI") { console.log(e.target.innerHTML); } });
•••• 自定义事件 ••••
最新的DOM标准允许我们自定义事件。直接看下面的例子。
var fakeNode = document.createElement('Coy'); // 创建一个自定义元素 var evt = document.createEvent('Event'); // 创建一个自定义事件 var evtType = 'test'; // 自定义事件的类型 // 事件监听函数 fakeNode.addEventListener(evtType, function(e){ console.log(e); // e.type === 'test'; }, false); // 初始化事件。 // initEvent用法:event.initEvent(type, bubbles, cancelable); evt.initEvent(evtType, false, false); // 向fakeNode派发evt事件 fakeNode.dispatchEvent(evt);
•••• 写在后面 ••••
事件,是前端开发中的一个基础。虽然比较简单,但是十分重要。本文总结了web事件的一些知识点,符合预期。




