DOM 的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象,比如XMLHttpRequest,也部署了这个接口。
该接口主要提供三个实例方法。
- addEventListener: 绑定事件
- removeEventListener: 移除事件
- dispatchEvent: 触发事件
以addEventListener
为例
btn.addEventListener('click',function(){console.log(this)},false)
第三个参数默认为false,可省略,指该事件只在冒泡阶段触发;若为true,则为在捕获阶段。
addEventListener方法可以为针对当前对象的同一个事件,添加多个不同的监听函数。这些函数按照添加顺序触发,即先添加先触发。
用队列理解,先进先出。
- 进:addEventListener
- 出:removeEventListener
如果为同一个事件多次添加同一个监听函数,该函数只会执行一次,多余的添加将自动被去除(不必使用removeEventListener方法手动去除)。
注意,removeEventListener方法移除的监听函数,必须是addEventListener方法添加的那个监听函数,而且必须在同一个元素节点,否则无效。
div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);
上面代码中,removeEventListener方法无效,因为监听函数不是同一个匿名函数。
监听函数
浏览器的事件模型,就是通过监听函数对事件做出反应。事件发生后,浏览器听到了这个事件,就会执行相应的监听函数。这就是事件驱动编程模式的主要编程方式。
JavaScript 有三种方法,可以为事件绑定监听函数。
1. addEventListener (推荐这种)
所有 DOM 节点实例都有addEventListener方法,用来为该节点定义事件的监听函数。
window.addEventListener('load', doSomething, false);
相比另外两种有如下优点:
- 同一个事件可以添加多个监听函数。
- 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数。
- 除了 DOM 节点,其他对象(比如window、XMLHttpRequest等)也有这个接口,它等于是整个 JavaScript 统一的监听函数接口。
2. 元素节点的事件属性
元素节点对象的事件属性,同样可以指定监听函数。
window.onload = doSomething;
div.onclick = function (event) {
console.log('触发事件');
};
使用这个方法指定的监听函数,也是只会在冒泡阶段触发。
注意,这种方法与 HTML 的on-属性的差异是,它的值是函数名doSomething
,而不像后者,必须给出完整的监听代码doSomething()
。
同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。因此,也不推荐使用。
3. HTML 的 on- 属性(不推荐)
<body onload="doSomething()">
<div onclick="console.log('触发事件')">
上面代码为body节点的load事件、div节点的click事件,指定了监听代码。一旦事件发生,就会执行这段代码。
注意,这些属性的值是将会执行的代码,而不是一个函数。
一旦指定的事件发生,on-属性的值是原样传入 JavaScript 引擎执行。因此如果要执行函数,不要忘记加上一对圆括号。
对象this、currentTarget和target
在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this、currentTarget和target包含相同的值。
1 var btn = document.getElementById("myBtn");
2 btn.onclick = function (event) {
3 alert(event.currentTarget === this); //ture
4 alert(event.target === this); //ture
5 };
这个例子检测了currentTarget和target与this的值。由于click事件的目标是按钮,一次这三个值是相等的。如果事件处理程序存在于按钮的父节点中,那么这些值是不相同的。
1 document.body.onclick = function (event) {
2 alert(event.currentTarget === document.body); //ture
3 alert(this === document.body); //ture
4 alert(event.target === document.getElementById("myBtn")); //ture
5 };
当单击这个例子中的按钮时,this和currentTarget都等于document.body,因为事件处理程序是注册到这个元素的。然而,target元素却等于按钮元素,以为它是click事件真正的目标。由于按钮上并没有注册事件处理程序,结果click事件就冒泡到了document.body,在那里事件才得到了处理。
currentTarget始终是监听事件者,而target是事件的真正发出者。
注意,在jQuery提供的on方法中,e.currentTarget与该方法接收的第二个参数有关,根据jQuery的文档描述
如果省略selector或者是null,那么事件处理程序被称为直接事件 或者 直接绑定事件 。每次选中的元素触发事件时,就会执行处理程序,不管它直接绑定在元素上,还是从后代(内部)元素冒泡到该元素的
当提供selector参数时,事件处理程序是指为委派事件(事件委托或事件代理)。事件不会在直接绑定的元素上触发,但当selector参数选择器匹配到后代(内部元素)的时候,事件处理函数才会被触发。jQuery 会从 event target 开始向上层元素(例如,由最内层元素到最外层元素)开始冒泡,并且在传播路径上所有绑定了相同事件的元素若满足匹配的选择器,那么这些元素上的事件也会被触发。
<!DOCTYPE html>
<html>
<head>
<script src="//code.jquery.com/jquery-1.9.1.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
<style>
li{
padding: 5px;
border: 1px solid red;
}
span{
border: 1px solid #000;
}
</style>
</head>
<body>
<ul>
<li><span>hello 1</span></li>
<li><span>hello 1</span></li>
<li><span>hello 1</span></li>
<li><span>hello 1</span></li>
</ul>
<script>
let ul = document.querySelectorAll('ul')[0]
let aLi = document.querySelectorAll('li')
$('ul').on('click','li',function(e){
console.log(e.target) // 被点击的元素
console.log(e.currentTarget) // li
console.log(e.currentTarget === this) // true
})
</script>
</body>
</html>
当li中含有子元素的时候,e.target指的是触发事件的元素,可能是span也可能是li,此时的e.currentTarget指的是selector那个参数,也就是本例中的li。如果省略selector参数,那么它和addEventListener中e.target和e.currentTarget是一致的。
事件委托
事件委托就是利用事件冒泡机制,指定一个事件处理程序,来管理某一类型的所有事件。
举个取快递的例子:
公司的员工们经常会收到快递。为了方便签收快递,有两种办法:一种是快递到了之后收件人各自去拿快递;另一种是委托前台MM代为签收,前台MM收到快递后会按照要求进行签收。
很显然,第二种方案更为方便高效,同时这种方案还有一种优势,那就是即使有新员工入职,前台的MM都可以代替新员工签收快递。
这个例子之所以非常恰当形象,是因为这个例子包含了委托的两层意思:首先,现在公司里的员工可以委托前台MM代为签收快递,即程序中现有的dom节点是有事件的并可以进行事件委托;其次,新入职的新员工也可以让前台MM代为签收快递,即程序中新添加的dom节点也是有事件的,并且也能委托处理事件。
如果一个ul中有100li,每个li都需要处理click事件,我们可以遍历所有li,给它们添加事件处理程序,这并不是理想的解决方案。
事件委托怎么实现呢?因为冒泡机制,既然点击子元素时,也会触发父元素的点击事件。那么我们就可以把点击子元素的事件要做的事情,交给最外层的父元素来做,让事件冒泡到最外层的dom节点上触发事件处理程序,这就是事件委托。
网友评论