事件是某些动作发生时产生的信号。JavaScript能够感知这些信号,因此能够编写代码来响应这些信号,也就是响应这些事件。而响应某个事件的函数就叫做事件处理程序(或事件侦听器)。事件处理程序的名字以on
开头,所以click
点击事件的事件处理程序就是onclick
,load
事件的事件处理程序就是onload
等等。为事件指定处理程序的方法有很多种,下面会一一介绍。
HTML事件处理
事件处理程序是发生事件时执行的程序,可以将事件处理函数作为HTML标签的一个属性来使用,事件处理程序指定方式的通用格式为:
<html_tag other_attributes eventhandler="javascript program">
例如要在一个按钮上执行某些JavaScript,可以像下面这样写代码:
<input type="button" value="点击" onclick="alert('你点击了按钮')">
当我们点击了这个按钮的时候就会弹出一个确认框。注意,因为onclick
的值是JavaScript,因此不能在其中使用未经转义的HTML字符,例如:&
、""
、<
和>
。
在HTML中定义的事件处理程序可以包含要执行的具体代码,也可以调用页面其它地方定义的函数。例如:
<script type="text/javascript"> function showMessage(){ alert("hello world!"); } </script> <input type="button" value="点击" onclick="showMessage()">
上面的例子中,单击按钮就会执行showMessage()
函数。showMessage()
函数可以在页面中的一个<script>
元素中定义,也可以在外部的js文件中定义。事件处理程序中的代码在执行的时候,有权访问全局作用域中的任何代码。
在HTML代码中指定事件处理程序有2个缺点:
- 1、HTML代码和JavaScript高度耦合,如果要更换事件处理程序需要更换两个地方:HTML代码和JavaScript代码。
- 2、当HTML代码出现的时候,它上面的事件处理程序可能还没有准备就绪,有可能会引发执行错误。例如:如果事件处理程序指定的JavaScript代码在页面的最底部定义,那么在页面解析到HTML元素的时候,相应的事件处理代码还没有被解析,这时执行HTML元素的事件处理程序就有可能会出现错误。
DOM0级事件处理程序
因为在HTML代码中直接指定事件处理程序存在一些缺陷,所以人们转而使用JavaScript来指定事件处理程序。通过JavaScript来指定事件处理程序的方式就是将一个函数赋值给一个事件处理属性。
在DOM0级事件处理程序中,要使用JavaScript来指定事件处理程序,首先需要获取操作对象的引用。每一个元素对象都有自己的事件处理属性,例如onclick
,将这些属性的值设置为一个函数,就可以指定事件处理程序了。例如:
<input type="button" value="点击" id="btn"> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.onclick = function(){ alert("你点击了按钮"); } </script>
使用DOM0级方法指定的事件处理程序被认为是元素的方法。这时候的事件处理程序是在元素的作用域中运行的,也就是说,在事件处理程序中,this
引用就是这个元素对象。例如:
<input type="button" value="点击" id="btn"> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.onclick = function(){ alert(this.id); //输出“btn” } </script>
我们可以在上事件处理程序中使用this
关键字来获取元素的所有属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。例如使用下面的代码可以查看事件冒泡的情况。
<html lang="zh" id="html" class="demo"> <head> <meta charset="UTF-8"> <title>事件冒泡</title> </head> <body id="body" class="demo"> <div id="parent" class="demo"> <button id="child" class="demo">点击一下</button> </div> </body> </html>
执行下面的JavaScript代码,这段代码使用DOM0级事件处理程序,处理程序中首先获取页面中所有的DOM元素,然后对每一个DOM元素进行循环遍历添加鼠标点击事件,最后在控制台中查看事件源和事件目标出现的先后顺序。
window.onload = function(){ var elements = document.querySelectorAll(".demo"); for(var i = 0 ; i < elements.length; i++){ elements[i].onclick = function(event){ var element = elements[i]; //获取事件 event = event?event:window.event; //获取事件发生的目标 target = event.target?event.target:event.srcElement; console.info("事件源:"+target.id+" , "+target.tagName+", 事件对象:"+this.id); } } }
下面是控制台中输出的结果:
从控制台中的输出结果可以看出,由上到下的顺序为child→parent→body→html,说明事件最先发生在按钮(child)上面,然后逐级往上冒泡。
DOM0事件处理程序会产生事件冒泡。如果要取消事件冒泡,可以使用event.stopPropagation()
方法,但是IE浏览器不支持这个方法,在IE中,取消事件冒泡的方法为:event.cancleBubble = true
。
//取消事件冒泡 event.stopPropagation(); //DOM方法 event.cancleBubble = true; //IE的方法
另外要提的一点是DOM0事件处理程序不支持事件捕获。
我们也可以删除通过DOM0级方法指定的事件处理程序,只要将事件处理程序属性的值设置为null
即可。
btn.onclick = null;
使用DOM0级事件处理程序最大的问题是事件会被覆盖。来看下面的例子:
<input type="button" value="点击" id="btn"> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.onclick = function(){ console.info("event-one"); } btn.onclick = function(){ console.info("event-two"); } btn.onclick = function(){ console.info("event-three"); } </script>
在上面的代码中,通过按钮的引用对象为按钮添加了3个事件处理函数,那么现在的问题是哪一个事件处理程序会被执行?答案是第三个事件处理程序会被执行,控制台中会输出“event-three”。这是因为btn.onclick
实际上就像一个指针,在执行第一个事件处理程序时它指向了内存中的一个引用,执行第二个事件处理程序时它又会指向另一个内存的引用,最终会指向第3个事件处理函数的内存引用。
DOM2级事件处理程序
由于DOM0级事件处理程序会产生事件冒泡和事件覆盖等问题,W3C组织制定了“DOM2级事件处理程序”。“DOM2级事件处理程序”定义了2个方法,用于处理指定和删除事件处理程序的操作,这2个方法是:addEventListener
和removeEventListener
。所有的DOM节点中都包含这两个方法,这些方法接收3个参数:要处理的事件名称、作为事件处理程序的函数和一个布尔值。布尔值参数如果设置为true
,表示在捕获阶段调用事件处理程序,如果设置为false
,表示在事件冒泡阶段调用事件处理程序。
如果要在一个按钮上添加DOM2级事件处理程序,可以使用下面的代码:
<input type="button" value="点击" id="btn"> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.addEventListener("click",function(){ alert(this.id); },false); </script>
上面的代码为按钮添加了一个DOM2级事件处理程序,事件名称是click
,对应DOM0级的onclick
属性。因为最后一个参数为false
,所以这个事件会在冒泡阶段被触发。
和DOM0级事件处理程序相同,DOM2级事件处理程序也会依附在元素的作用域中运行。
使用DOM2级事件处理程序就可以避免DOM0级事件处理程序的事件覆盖问题。例如下面的例子:
<input type="button" value="点击" id="btn"> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.addEventListener("click",function(){ console.info("event-one"); },false); btn.addEventListener("click",function(){ console.info("event-two"); },false); btn.addEventListener("click",function(){ console.info("event-three"); },false); </script>
查看控制台我们会发现,3个事件都会被执行,控制台中依次打印出:“event-one”、“event-two”和“event-three”。
接下来,我们可以来看一下事件捕获的情况。上一篇文章只是介绍了事件捕获,并没有举一个具体的例子。下面我们在DOM2级事件处理程序中来查看事件捕获的过程。代码如下:
<html lang="zh" id="html" class="demo"> <head> <meta charset="UTF-8"> <title>事件捕获</title> </head> <body id="body" class="demo"> <div id="parent" class="demo"> <button id="child" class="demo">点击一下</button> </div> <script type="text/javascript"> window.onload = function(){ var elements = document.querySelectorAll(".demo"); for(var i = 0 ; i < elements.length; i++){ elements[i].addEventListener("click",function(event){ var element = elements[i]; //获取事件 event = event?event:window.event; //获取事件发生的目标 target = event.target?event.target:event.srcElement; console.info("事件源:"+target.id+" , "+target.tagName+", 事件对象:"+this.id); },true); } } </script> </body> </html>
由于在上面的事件处理程序中,第3个参数被设置为true
,表示在捕获阶段调用事件处理程序。在Firefox浏览器的控制台中得到下面的结果:
从控制台的结果中可以看到,事件源是按钮,事件对象从上往下分别为:
<document>
→<body>
→<parent>
→<child>
。
通过addEventListener()
方法添加的事件处理程序只能通过removeEventListener()
方法来移除。移除时传入的参数和添加事件处理程序时的参数一样。注意:通过addEventListener()
方法添加的匿名函数将无法移除。下面是一个移除DOM2级事件处理程序的例子:
var btn = document.getElementById("btn"); btn.addEventListener("click",function(){ console.info(this.id); },false); ...... btn.removeEventListener("click",function(){/*函数在此处没有用*/},false);
传入removeEventListener()
中的事件处理函数必须与addEventListener()
中的事件处理函数相同,例如:
var fn = function(){ console.info(this.id); } var btn = document.getElementById("btn"); btn.addEventListener("click",fn,false); ...... btn.removeEventListener("click",fn,false);
在多数情况下,都是将事件处理程序添加到事件流的事件冒泡阶段,这样可以最大限度的兼容各种浏览器。最好只在事件到达目标之前截获它的时候将事件处理程序添加到事件捕获阶段。
Firefox、Chrome、Safari和Opera浏览器都支持DOM2级事件处理程序。
IE中的事件处理程序
IE浏览器不支持DOM2级事件处理程序,但是它有2个类似的方法:attachEvent()
和detachEvent()
。这两个方法接收相同的参数:事件处理程序的名称和事件处理程序函数。由于IE浏览器只支持事件冒泡,所以通过attachEvent()
添加的事件处理程序都会被添加到事件冒泡阶段。
在IE中使用attachEvent()
方法为一个按钮添加事件处理程序的代码如下:
var btn = document.getElementById("btn"); btn.attachEvent("onclick",function(){ alert("你点击了按钮"); });
注意,attachEvent()
方法的第一个参数是onclick
,而不是DOM2级事件处理程序中使用的click
。
attachEvent()
方法和DOM0级方法的主要区别在于事件处理程序的作用域。在DOM0级事件处理程序中,事件处理程序会在其所属的元素的作用域内运行。而在使用attachEvent()
方法的情况下,事件处理程序会在全局作用域内运行,此时,this
等于window
。例如:
var btn = document.getElementById("btn"); btn.attachEvent("onclick",function(){ alert(this === window); //true });
在编写跨浏览器的事件处理代码时,记住这一个区别非常重要。
和addEventListener()
方法相同,attachEvent()
方法也可以为一个元素添加多个事件处理函数。
<input type="button" value="点击" id="btn"> <script type="text/javascript"> var btn = document.getElementById("btn"); btn.attachEvent("onclick",function(){ console.info("event-one"); }); btn.attachEvent("onclick",function(){ console.info("event-two"); }); btn.attachEvent("onclick",function(){ console.info("event-three"); }); </script>
与DOM2级事件处理程序不同的是,在旧版本的IE浏览器中使用attachEvent()
方法为一个元素添加多个事件处理程序时,它们不是以添加的顺序来执行的,而是以相反的顺序被执行。例如上面的代码,控制台中输出的结构为:“event-three”,“event-two”和“event-one”。在新版本的IE(IE10+)浏览器中,它的行为与DOM2级事件处理程序相同。
使用attachEvent()
来添加的事件可以通过detachEvent()
方法来移除。它们提供的参数必须相同。与DOM方法一样,使用attachEvent()
添加的匿名事件处理函数不能被移除。移除事件的代码如下:
var fn = function(){ console.info(this.id); } var btn = document.getElementById("btn"); btn.attachEvent("onclick",fn); ...... btn.detachEvent("onclick",fn);
编写跨浏览器的事件处理程序
如果要保证事件处理程序在大多数浏览器中都能够正常运行,我们只需要关注事件冒泡阶段。
我们可以创建两个方法,一个用于跨浏览器添加事件处理程序,一个用于跨浏览器删除事件处理程序。
跨浏览器添加事件处理程序要根据实际情况来使用DOM0级方法、DOM2级方法或IE方法来添加事件。我们可以给这个方法取名为addEventHandler()
,该方法接收3个参数:要操作的元素、事件的名称和事件处理函数。
另一个方法是用于删除事件处理程序,取名为removeEventHandler()
,它也接收同样的参数。它的责任是移除先前添加的事件处理程序。
/* 添加事件处理程序 */ function addEventHandler(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; } } /* 删除事件处理程序 */ function removeEventHandler(element,type,handler){ if(element.removeEventListener){ element.removeEventListener(type,handler,false); }else if(element.detachEvent){ element.detachEvent("on"+type,handler); }else{ element["on" + type] = null; } }
这两个方法首先都会判断传入的元素是否存在DOM2级方法。如果存在DOM2级方法则使用该方法:传入事件类型、事件处理函数和布尔值false
(表示冒泡阶段)。如果存在的是IE方法,则使用第二种条件,注意要为事件类型添加“on”前缀。最后一种可能性是DOM0级事件处理程序,在现代浏览器中一般都不会执行这个条件。
可以像下面这样来使用上面的方法:
var btn = document.getElementById("btn"); var fn = function(){ alert("你点击了按钮"); } addEventHandler(btn,"click",fn); ...... removeEventHandler(btn,"click",fn);