在浏览器或文档某个元素发生某个特定情况的瞬间,会作为一个事件进行广播,我们可以对其添加监听来处理特定的事件。
事件流
事件流描述了页面中接收事件的顺序。
整个事件流包含了三个阶段:事件捕获阶段、事件目标阶段和事件冒泡阶段。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test</title> 5 </head> 6 <body> 7 <div id="myDiv">Click me</div> 8 </body> 9 </html>
如果我们点击了上面代码中的div元素,实际的事件流如下:
- 捕获阶段:1、2、3属于捕获阶段;
- 目标阶段:4属于目标阶段;
- 冒泡阶段:5、6、7属于冒泡阶段;
事件处理
在HTML中,事件的处理有多种方法可以用来实现。下面我们一一来看。
HTML事件处理
即直接在HTML中指定处理的函数:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test</title> 5 </head> 6 <body> 7 <div id="myDiv" onclick="alert(this.id)//myDiv">Click me</div> 8 </body> 9 </html>
也可以指定为一个外部定义的函数:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test</title> 5 </head> 6 <body> 7 <script type="text/javascript"> 8 function divClickHandler(event) { 9 console.log(this.id); // undefined 10 console.log(event.type); // click 11 } 12 </script> 13 <div id="myDiv" onclick="divClickHandler(event)">Click me</div> 14 </body> 15 </html>
注意这种方式的话,作用域是全局的,而上面的写法可以通过this.id取到点击的对象的id;
另外,事件会有一个名为event的参数,可以传递给处理的函数来得知更加丰富的事件信息。
DOM0级事件处理
实际上就是HTML事件处理的代码指定写法,如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test</title> 5 </head> 6 <body> 7 <div id="myDiv" onclick="divClickHandler(event)">Click me</div> 8 <script type="text/javascript"> 9 function divClickHandler(event) { 10 console.log(this.id); // myDiv 11 console.log(event.type); // click 12 } 13 14 var myDiv = document.getElementById("myDiv"); 15 myDiv.onclick = divClickHandler; 16 </script> 17 </body> 18 </html>
使用该方法可以取消事件的绑定,如下:
1 myDiv.onclick = null;
这种方式比较简单,但是无法绑定2个函数。
DOM2级事件处理
新的标准中,关于事件的处理采用了观察者模式,添加了addEventListener和removeEventListener两个方法,这两个方法都接受3个参数:
- 事件类型
- 处理该事件的函数
- 最后一个布尔值:true表示捕获阶段调用,false表示冒泡阶段调用,默认false,需要注意目标阶段也算在冒泡阶段中
我们看下例子:
1 var divClickHandler = function(event) { 2 console.log(this.id); // myDiv 3 console.log(event.type); // click 4 } 5 6 var myDiv = document.getElementById("myDiv"); 7 myDiv.addEventListener("click", divClickHandler);
需要注意的是使用该方法可以为一个事件类型添加多个事件处理监听,使用removeEventListener可以移除添加了的监听,我们再看一个例子:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <title>Test</title> 5 </head> 6 <body> 7 <div id="myGroup"> 8 <div id="myDiv">Click me</div> 9 </div> 10 <script type="text/javascript"> 11 function buhuo(event) { 12 console.log(1); 13 } 14 function maopao(event) { 15 console.log(2); 16 } 17 18 var myGroup = document.getElementById("myGroup"); 19 myGroup.addEventListener("click", buhuo, true); 20 myGroup.addEventListener("click", maopao); 21 22 function divClickHandler(event) { 23 console.log(3); 24 } 25 26 var myDiv = document.getElementById("myDiv"); 27 myDiv.addEventListener("click", divClickHandler); 28 </script> 29 </body> 30 </html>
点击后输出如下:
1 1 2 3 3 2
IE事件处理
在早期的IE中,使用的是attachEvent和detachEvent,由于不是标准的方法,所以这里就不记录了,有兴趣的童鞋可以自行搜索。
事件对象
在触发DOM上的某个事件时,将会产生一个事件对象event,这个对象包含所有与当前事件有关的信息。
DOM中的事件对象
无论是DOM0还是DOM2级,都会传入event对象,如下:
1 var myDiv = document.getElementById("myDiv"); 2 myDiv.onclick = function(event) { 3 console.log(event.type); // click 4 } 5 myDiv.addEventListener("click", divClickHandler); 6 function divClickHandler(event) { 7 console.log(event.type); // click 8 }
即使直接写在HTML中(DOM0级的另一种写法),也一样存在event事件对象:
<div id="myDiv" onclick="console.log(event.type)">Click me</div>
target与currentTarget
在事件处理函数中,this对象始终指向currentTarget对象,target对象包含事件的实际目标(即处于目标阶段的对象)。而currentTarget对象指向添加了当前事件处理函数的对象。
我们来看下面的例子:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>Test</title> 6 </head> 7 <body id="myBody"> 8 <div id="myGroup"> 9 <div id="myDiv" onclick>Click me</div> 10 </div> 11 <script type="text/javascript"> 12 var body = document.body; 13 var myGroup = document.getElementById("myGroup"); 14 var myDiv = document.getElementById("myDiv"); 15 16 body.addEventListener("click", onBodyClick); 17 function onBodyClick(event) { 18 console.log("click body: " + event.currentTarget.id + ", " + event.target.id); 19 } 20 21 myGroup.addEventListener("click", onMyGroupClick); 22 function onMyGroupClick(event) { 23 console.log("click myGroup: " + event.currentTarget.id + ", " + event.target.id); 24 } 25 26 myDiv.addEventListener("click", onMyDivClick); 27 function onMyDivClick(event) { 28 console.log("click myDiv: " + event.currentTarget.id + ", " + event.target.id); 29 } 30 </script> 31 </body> 32 </html>
点击Click me后打印如下:
click myDiv: myDiv, myDiv
click myGroup: myGroup, myDiv
click body: myBody, myDiv
取消默认行为
事件可以取消默认行为,什么是默认行为呢?比如一个设定了目标地址的a标签,默认点击后会打开对应的地址,这个不是我们添加的行为就是默认行为。我们可以通过event对象的cancelable来判断是否可以取消默认行为,为true表示可以取消,调用preventDefault方法就可取消默认行为,如下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>Test</title> 6 </head> 7 <body> 8 <a id="myLink" href="http://www.baidu.com">link</a> 9 <script type="text/javascript"> 10 var myLink = document.getElementById("myLink"); 11 myLink.addEventListener("click", linkClickHandler); 12 13 function linkClickHandler(event) { 14 if (event.cancelable) 15 event.preventDefault(); 16 } 17 </script> 18 </body> 19 </html>
正常情况下点击link会进行跳转,加入取消默认事件的代码后点击link后就没有跳转网页的执行了。
取消事件冒泡
我们知道事件是会向根对象进行冒泡的,我们同样可以通过代码暂停事件继续进行冒泡,有两个方法可以使用:
- stopPropagation:取消事件向父级冒泡;
- stopImmediatePropagation:取消事件向父级及同级的冒泡;
这两个方法都是在bubbles属性为true时可以使用。
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>Test</title> 6 </head> 7 <body> 8 <div id="myDiv">click me</div> 9 <script type="text/javascript"> 10 var myDiv = document.getElementById("myDiv"); 11 myDiv.addEventListener("click", divClickHandler); 12 myDiv.addEventListener("click", divClickHandler2); 13 14 function divClickHandler(event) { 15 if (event.bubbles) 16 event.stopPropagation(); 17 console.log("1"); 18 } 19 20 function divClickHandler2(event) { 21 console.log("2"); 22 } 23 24 document.body.addEventListener("click", bodyClickHandler); 25 26 function bodyClickHandler(event) { 27 console.log("3"); 28 } 29 </script> 30 </body> 31 </html>
调用stopPropagation后打印“1”和“2”,表示阻止了向父级的冒泡,但是同级的事件监听仍然可以接收事件。
修改代码如下后:
1 function divClickHandler(event) { 2 if (event.bubbles) 3 event.stopImmediatePropagation(); 4 console.log("1"); 5 }
只会打印“1”表示阻止了后续的所有事件,包含同级及父级的事件。
事件类型
浏览器中,已经定义了许多事件相关的类型,大体如下:
http://www.w3school.com.cn/jsref/dom_obj_event.asp
内存和性能
这里讨论下提升性能的方法。
事件委托
在一些其它语言的GUI项目中,为每个按钮添加事件处理函数是很正常且也不会影响性能的,但是在JS中,每个函数都是对象,如果为每个点击项都额外添加一个处理函数,无疑会增加消耗,这时采用冒泡的特性在父级对象上添加一个函数,通过target属性的id来区分不同的点击项可以减少页面中的处理函数了。
及时移除不需要的事件监听
对于不使用了的事件监听,请及时移除,比如在div内的一个元素存在监听,在没有移除监听之前就更改了innerText导致移除了内部元素但是没有移除监听就有可能导致移除元素无法垃圾回收。
模拟事件
在JS中,我们还可以通过代码创建一个特定的事件进行播放来进行事件的模拟,下面我们来看看如何模拟一个系统的事件。
模拟鼠标事件
我们看看模拟鼠标事件的代码:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <title>Test</title> 6 </head> 7 <body> 8 <a id="myLink" href="http://www.baidu.com">click me</a> 9 <script type="text/javascript"> 10 setTimeout(function(){ 11 var myLink = document.getElementById("myLink"); 12 //创建鼠标事件 13 var evt = document.createEvent("MouseEvents"); 14 //初始化鼠标事件 15 evt.initMouseEvent("click", true, true, document.defaultView); 16 //在目标对象上播放 17 myLink.dispatchEvent(evt); 18 }, 3000); 19 </script> 20 </body> 21 </html>
3秒之后会打开百度首页。
initMouseEvent的参数描述点击这里。
模拟其它事件
注意createEvent的参数,使用“MouseEvents”创建鼠标事件,使用“HTMLEvents”可以创建页面事件,使用“MutationEvents”可以创建DOM的变动事件。具体的使用方法可以自行搜索。
自定义事件
同样的,我们也可以创建自定义的事件,如下:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Test</title> </head> <body> <a id="myLink" href="http://www.baidu.com">click me</a> <script type="text/javascript"> document.body.addEventListener("myEvent", function(event){ console.log("onMyEvent name: " + event.detail.name); }); setTimeout(function(){ var myLink = document.getElementById("myLink"); //创建自定义事件 var evt = document.createEvent("CustomEvents"); //初始化自定义事件 evt.initCustomEvent("myEvent", true, true, {name:"Li Lei"}); //在目标对象上播放 myLink.dispatchEvent(evt); }, 3000); </script> </body> </html>
但是,CustomEvents并不是所有浏览器都支持的事件类型,比如Chrome就不支持,所以,当我们需要使用到跨浏览器的自定义事件时,一般还是使用观察者模式来实现,当然自己实现的话就不能拥有冒泡的功能了。