浅谈JavaScript的事件(事件处理程序)
事件就是用户或者浏览器自身执行的某种动作。诸如click、load和mouseover,都是事件的名字。而响应某个事件的函数就叫事件处理程序。事件处理程序的名字以“on”开头,比如click事件的事件处理程序是onclick。为事件指定事件处理程序的方式有多种方式。
- HTML事件处理程序
元素支持的事件,都可以使用与相应事件处理程序同名的HTML特性来指定。这个特性的值能支持一定的JavaScript代码。例如,在单击按钮的时候执行一些JavaScript代码。 <div id="aa" onclick="console.log('div')" style="width: 100px;">2222</div> ,当单击这个div的时候,会在浏览器的控制台中输出div。这个特性是通过JavaScript来实现的,不能在其中使用未经转义的HTML语法字符,例如和号(&)、双引号("")、单引号('')、小于号(<)或者大于号(>)。
在HTML中定义的事件处理程序可以包含要执行的具体动作,也可以调用定义在其他页面的脚本。
1 function divClick(e){ 2 var target=e.target; 3 console.log("div"); 4 //stopBubble(e); 5 }
<div id="aa" onclick="divClick(event);" style="width: 100px;">2222</div>
上面的代码中,点击div之后就会调用divClick函数。这个函数是单独定义的script脚本中的,当然也可以定义在一个外部文件中。事件处理程序中的代码,可以访问全局的方法。上面的代码中,同样可以传递event参数以及this参数。event参数能够获取事件的类型参数等,通过this能够获取点击对象本身。
1 function divClick(e){ 2 //var target=e.target; 3 console.log($(e).text());//222 4 //stopBubble(e); 5 }
<div id="aa" onclick="divClick(this);" style="width: 100px;">2222</div>
在HTML中指定事件处理程序有一定的缺点:存在一定的时差,用户可能在页面一出现就触发相应的事件,但是事件处理程序尚不具备执行的条件。上面的例子上,如果divClick函数定义在div的下方,我们在函数尚未解析之前,就点击div,这样就会导致报错。
另一个缺点是,这样的事件处理程序的作用域链在不同的浏览器中会导致不同结果。不同JavaScript引擎遵循的标识符解析规则略有差异,很可能在访问非限定对象时出错。
通过HTML指定事件处理程序的最后一个缺点是HTML与JavaScript代码的紧密耦合。如果要更换事件处理程序就需要改动两个地方:JavaScript和HTML。
- DOM级事件处理程序
通过JavaScript指定事件处理程序的传统方式是将一个函数赋值给事件处理程序属性。通过JavaScript指定事件处理程序有两个优势:简单和浏览器兼容性好。要使用JavaScript指定事件处理程序,首先必须获取一个元素的对象引用。每个元素都有自己的事件处理程序,这个属性通常是全部小写,比如onclick。
1 <div id="aa" style="width: 100px;">2222</div> 2 <script> 3 var a=document.getElementById("aa"); 4 a.onclick=function(e){ 5 var target=e.target; 6 var text=target.innerHTML; 7 console.log(text);//222 8 } 9 </script>
上面的代码,通过文档对象获取了对象的引用,然后为它指定了onclick事件处理程序。e为点击事件的参数,通过该参数能够获取点击事件的对象,即target。通过对象可以进一步获取对象的属性。
1 <div id="aa" style="width: 100px;">2222</div> 2 <script> 3 $("#aa").click(function(e){ 4 var target=e.target; 5 var text=target.innerHTML; 6 console.log(text);//222 7 }); 8 </script>
这段代码与上面的例子实现的是一样的效果,但是这个是通过JQuery来实现的。
DOM2级事件处理程序定义了两个方法,用于处理指定和删除事件处理程序的操作:addEventListener和removeEventListener。所有DOM节点都包含这两个方法,并且他们接收3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。布尔值如果为true,表示在捕获阶段执行事件处理程序,如果为false,表示在冒泡阶段调用事件处理程序。
1 <div id="aa" style="width: 100px;">2222</div> 2 <script> 3 var a=document.getElementById("aa"); 4 a.addEventListener("click",function(e){ 5 console.log(this.id); 6 },false); 7 </script>
上面的例子通过addEventListener为元素aa添加了一个click事件。通过事件处理程序能够访问到元素,this和元素处在同一个作用域链。
通过DOM2级可以通过添加多个事件处理程序。事件处理程序会按照添加的顺序依次触发。
1 var a=document.getElementById("aa"); 2 a.addEventListener("click",function(e){ 3 console.log(this.id); 4 },false); 5 a.addEventListener("click",function(e){ 6 console.log(this.innerHTML); 7 },false);
通过addEventListener添加的事件处理程序,可以通过removeEventListener来移除事件处理程序。但是,有时候我们会走入一个误区。
1 var a=document.getElementById("aa"); 2 a.addEventListener("click",function(e){ 3 console.log(this.id); 4 },false); 5 a.removeEventListener("click",function(e){ 6 console.log(this.id); 7 },false);
上面的代码我们使用removeEventListener方法移除事件处理程序,但是并没有起作用。在使用addEventListener和removeEventListener的时候,第二个事件处理程序函数必须是同一个函数才会有作用,我们对上面的代码作一个修改,就可以了。
1 var a=document.getElementById("aa"); 2 var callback=function(e){ 3 console.log(this.id); 4 } 5 a.addEventListener("click",callback,false); 6 a.removeEventListener("click",callback,false);
上面的代码中,我们在addEventListener和removeEventListener中调用的是同一个方法,所以元素aa已经没有点击事件。
对于IE8以及IE8以下浏览器来说,它们并没有上述的两个方法,但是提供了attachEvent和detachEvent两个方法。这两个只需要传递两个参数:第一参数事件程序名称,第二个事件处理程序函数。
1 var a=document.getElementById("aa"); 2 var callback=function(e){ 3 console.log(e.target.innerHTML); 4 } 5 //a.addEventListener("click",callback,false); 6 //a.removeEventListener("click",callback,false); 7 a.attachEvent("onclick",callback);
上面的代码通过attachEvent添加了事件处理程序,但是attachEvent与addEventListener不一样。在attachEvent的事件处理程序函数中this是指向window的,我们无法获取元素对象。
1 var a=document.getElementById("aa"); 2 var callback=function(e){ 3 console.log(this.id); 4 } 5 //a.addEventListener("click",callback,false); 6 //a.removeEventListener("click",callback,false); 7 a.attachEvent("onclick",function(e){ 8 callback.call(a,e); 9 });
上面的代码,我们通过call修改了this的指向,但是这样会带来另外一个问题,怎么detachEvent?
- 跨浏览器事件处理程序
为了以跨浏览器的事件处理程序,开发人员可以封装适合自己的js库。
1 var EventUtil={ 2 addEvent:function(element,type,fn){ 3 if(element.addEventListener){ 4 element.addEventListener(type,fn,false); 5 } 6 else if(element.attachEvent){ 7 element.attachEvent("on"+type,fn); 8 } 9 else{ 10 element["on"+type]=fn; 11 } 12 }, 13 removeEvent:function(element,type,fn){ 14 if(element.removeEventListener){ 15 element.removeEventListener(type,fn,false); 16 } 17 else if(element.detachEvent){ 18 element.detachEvent("on"+type,fn); 19 } 20 else{ 21 element["on"+type]=null; 22 } 23 } 24 }; 25 var aa=document.getElementById("aa"); 26 var func=function(e){ 27 console.log(e.type); 28 EventUtil.removeEvent(aa,"click",func); 29 } 30 EventUtil.addEvent(aa,"click",func);
上面的EventUtil封装了一个跨浏览器的对象,包含两个方法addEvent和removeEvent。在第25行获取元素对象引用,26行定义了fn函数,30行调用addEvent添加了事件处理程序。这个事件处理程序只能执行一次,因为我们在func函数中又调用了removeEvent函数。