事件是JavaScript和HTML之间交互的手段,指的是文档或者浏览器窗口发生的一些特定交互。我们可以使用处理程序来预订事件,以便事件发生时执行相应的代码,在软件工程中,这种模式称之为观察者模式。
一 事件流、事件冒泡和事件捕获
当浏览器发展到第四代的时候,IE4和Netscape Communicator4两个团队遇到了一个问题。即页面上有一组同心圆,当点击圆心时,到底是这一组同心圆的哪个触发了事件?两个团队对整个问题给出了相同的认知,当圆心被点击时,同时被点击的还有它的父容器,父容器的父容器,直至整个页面。
事件流描述的是从页面中接收事件的顺序。
但是两个团队对事件流的顺序持截然相反的态度。
IE团队认为事件是从子元素到页面的事件冒泡流;而Netscape则认为事件是从页面层层传递到具体元素的事件捕获流。
二 DOM事件流
DOM2级事件规定的事件流包括三个阶段:事件捕获阶段,处于目标阶段,事件冒泡阶段。
以下面的简单页面为例,单击div元素会按照下图的顺序触发事件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Bubbling</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>
DOM2级规范中表明:在事件流中,事件的目标(div元素)在捕获阶段不会接收到事件。这意味着在捕获阶段从1->2->3就结束了,下个阶段是“处于目标”阶段,于是事件在div上发生,并在事件处理中被看成是冒泡阶段的一部分。然后,冒泡阶段发生,事件又传播回文档。
上述的规范表明事件在捕获阶段是不会被触发的。
多数的浏览器在实现的时候都遵循了上述规范,但是IE9、Safari、Chrome、Firefox和Opera9.5及更高的版本都会在捕获阶段触发事件对象上的事件。结果,就是有两个机会在目标对象上面操作对象。
三 事件处理程序
响应事件的函数就叫做事件处理程序。为事件指定处理程序的方式有好几种,包括HTML事件处理程序、DOM0级事件处理程序和DOM2级事件处理程序以及IE事件处理程序。
3.1 HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值可以使一些能够执行的JavaScript代码,例如下面这样:
<input type="button" value="Click Me" onclick="alert('clicked!')">
<input type="button" value="Click Me" onclick="showMessage(this.value)">
除了像上述一样直接写可执行的JavaScript,还可以调用外部script或者js文件的方法。
这样指定事件处理程序可以创建一个event的事件对象,同时this的指向也等于事件的目标元素。
在创建的showMessage函数中,可以通过with来扩展它的作用域,可以像访问局部变量一样访问document及该元素本身的成员,如果input存在与form表单中,作用域也可以包含访问表单元素的入口,例如:
function showMessage(value){
with(document){
with(this.form){
with(this){
//do something...
}
}
}
}
但是,这种声明事件处理程序的方法存在几个问题:
一是触发事件时事件还没加载的时差问题,
二是HTML和JS高度耦合的问题。因此现在很少使用HTML事件处理程序,转而使用JavaScript指定事件处理程序。
3.2 DOM0级事件处理程序
通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性,如下:
var mybtn = document.getElementById("myBtn");
mybtn.onclick = function(){
alert(this.id)
}
如果这些代码在HTML之后,那么在代码没有运行到事件处理程序之前,点击效果不存在。
this指向的是目标元素,并且事件在冒泡阶段被捕获。
要删除该事件处理程序使用如下方法:
mybtn.onclick = null
3.3 DOM2级事件处理程序
DOM2级事件定义了两个方法,用于添加和删除事件处理程序:addEventListener()和removeEventListener()。所以DOM节点都包含这两个方法,并且它们都接收三个参数,要处理的事件名,事件处理函数,一个布尔值。布尔值为true,表示在捕获阶段调用事件处理程序,否则在冒泡阶段调用。
var mybtn = document.getElementById("myBtn");
mybtn.addEventListener('click',function(){
alert(this.id)
},false)
使用DOM2级事件处理程序可以添加多个。
var mybtn = document.getElementById("myBtn");
mybtn.addEventListener('click',function(){
alert(this.id)
},false)
mybtn.addEventListener('click',function(){
alert(this.value)
},false)
移除事件处理程序只能使用removeEventListener(),并且传入的事件处理函数必须和传入的相同,所以,我们应该在添加事件时将事件处理函数先赋值给一个变量,这样才能保证移除时的函数相同。
var mybtn = document.getElementById("myBtn");
var handler = function(){
alert(this.id)
}
mybtn.addEventListener('click',handler,false)
mybtn.removeEventListener('click',handler,false)
大多数时候,我们都将事件处理程序添加在冒泡阶段,这样可以最大限度的兼容各种浏览器。
3.4 IE事件处理程序
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。接收参数为事件处理名称和事件处理函数,由于IE8及更早的版本只支持事件冒泡,所以这两个方法都是添加在事件冒泡阶段。
var mybtn = document.getElementById("myBtn");
var handler = function(){
alert(this.id)
}
mybtn.attachEvent('onclick',handler)
mybtn.detachEvent('onclick',handler)
使用IE添加的事件处理程序的作用域和DOM0级的方法不一样,事件处理程序在全局作用域中运行,this指向window。
并且,通过attachEvent添加的多个事件的顺序和DOM2级不一样,它是按照相反的顺序触发,即后添加的事件先触发。
3.5 跨浏览器的事件处理程序
既然事件处理程序因浏览器而不同,我们可以选择一些JavaScript库来实现,同时也可以自己实现跨浏览器的事件处理程序。
var EventUtils = {
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if (element.attachEvent){
element.attachEvent("on"+type,handler);
}else {
element["on"+type] = handler;
}
},
removeHandler:function(){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if (element.detachEvent){
element.detachEvent("on"+type,handler);
}else {
element["on"+type] = null;
}
}
}
四 事件对象
触发事件时,会产生一个事件对象event,这个对象包含着所有与事件有关的信息。
4.1DOM中的事件对象
通过DOM0和DOM2指定事件处理程序时,都会传入event对象,就如下面的例子。
var mybtn = document.getElementById("myBtn");
mybtn.addEventListener('click',function(event){
alert(event.type)
},false)
mybtn.onclick = function(event){
alert(event.type)
}
通过HTML添加事件处理程序时,变量event中也保存着event对象。
<input type="button" value="Click Me" onclick="alert(event.type)">
event中包含的常用元素有:
bubbles:表明事件是否冒泡
cancelable:表明是否可以取消事件的默认行为
defaultPrevented:为true表示已经调用了preventDefault()
detail:与事件相关的细节
eventPhase:调用事件处理程序的阶段。
type:事件类型
target:事件的目标
currentTarget:其事件处理程序当前正在处理事件的那个元素
preventDefault()
stopPropagation()
在这些事件属性中,对象this始终等于currentTarget的值,他们等于这个事件挂载的dom节点。而target则只包含事件的实际目标,即不管事件是在哪里被捕获的,最初点击的那个目标就等于target。
document.body.onclick = function(event){
alert(event.currentTarget === document.body); //true
alert(this === document.body); //true
alert(event.target === document.getElementById("myBtn")); //true
}
4.2IE中的事件对象
在使用DOM0级添加事件处理程序时,event对象作为window对象的一个属性存在,可以使用如下方法获得:
var mybtn = document.getElementById("myBtn");
mybtn.onclick = function(){
var event = window.event; //IE中
alert(event.type)
}
如果是用attachEvent添加的,那么event可以作为参数传入,同时也可以通过window的属性来获得。如果是HTML特性指定的事件处理程序,那么和DOM中一样,通过event变量访问event对象。
IE的event对象同样也包含很多属性和方法。其中最重要的又如下几个:
srcElement:事件的目标,和target一样
returnFalse:默认值为true,如果设置为false就可以取消事件的默认行为。
cancelBubble:默认值为false,将其设置为true就可以取消事件冒泡。
type:事件类型
4.3跨浏览器的事件对象
事件对象在不同浏览器也不相同,所以扩展之前的EventUtil可以实现跨浏览器的增强。
var EventUtils = {
addHandler:function(element,type,handler){
if(element.addEventListener){
element.addEventListener(type,handler,false);
}else if (element.attachEvent){
element.attachEvent("on"+type,handler);
}else {
element["on"+type] = handler;
}
},
removeHandler:function(){
if(element.removeEventListener){
element.removeEventListener(type,handler,false);
}else if (element.detachEvent){
element.detachEvent("on"+type,handler);
}else {
element["on"+type] = null;
}
},
getEvent:function(event){
return event ? event : window.event;
},
getTarget:function(event){
return event.target || event.srcElement;
},
preventDefault:function(event){
if(event.preventDefault){
event.preventDefault()
}else{
event.returnFalse = false;
}
},
stopPropagation:function(event){
if(event.stopPropagation){
event.stopPropagation()
}else{
event.cancelBubble = true;
}
}
}
五 内存和性能
在页面添加事件处理程序会导致页面性能变慢,可以通过一下几种手段来优化结构,提高性能。
1.使用事件委托
事件委托利用了事件冒泡的机制,只指定一个事件处理程序,来管理某一类型的所有事件。在JS的一些库中已经实现了事件委托,我们可以在开发过程中方便的使用,当然也可以手写一个事件委托,可以在参考这里【面试】手写原生js实现事件代理,并兼容浏览器
2.及时移除事件处理程序
不再使用事件时,应该及时的移除事件处理程序,以释放内存,移除时应注意事件函数必须是添加时的函数。
以上就是所有有关JS事件的核心概念,如有问题,欢迎交流。