深入理解DOM事件机制系列第二篇——事件处理程序
前面的话
事件处理程序又叫事件侦听器,实际上就是事件的绑定函数。事件发生时会执行函数中相应代码。事件处理程序有HTML事件处理程序、DOM0级事件处理程序、DOM2级事件处理程序和IE事件处理程序四类,下面将详细介绍该部分内容
HTML事件处理程序
某个元素支持的每种事件,都可以使用一个与相应事件处理程序同名的HTML特性来指定。这个特性的值应该是能够执行的javascript代码
在事件处理程序函数内部,this值等于事件的目标元素
<div id="box" style="height:30px;width:200px;background-color:pink;"onclick = "this.innerHTML+= '1';"></div>
在HTML中定义的事件处理程序也可以调用在页面其他地方定义的脚本
<div id="box" style="height:30px;width:200px;background-color:pink;"onclick = "test()"></div> <script> function test(){box.innerHTML+= '1';} </script>
HTML事件处理程序会创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。通过event变量,可以直接访问事件对象,不用自己定义它,也不用从函数的参数列表中获取
<div id="box" style="height:30px;width:200px;background-color:pink;"onclick = "this.innerHTML+= event.type;"></div>
在事件处理程序函数内部,可以像访问局部变量一样访问document及该元素本身的成员。如此一来,事件处理程序要访问自己的属性就简单多了
<button id="box" value="test" style="height:30px;width:200px;background-color:pink;"onclick = "this.innerHTML+= value;"></button>
【扩展】
下列这种情况输出的是空字符串'',如果与预想结果不一致,请移步至此
<script> var value=123; </script> <button style="height:30px;width:200px;background-color:pink;"onclick = "this.innerHTML+= value;"></button>
缺点
【1】时差问题
因为用户可能会有HTML元素一出现在页面上时就触发相应的事件,但当时的事件处理程序有可能尚不具备执行条件,就会报错
<button style="height:30px;width:200px;background-color:pink;"onclick = "this.innerHTML+= val;"></button> <script src="http://www.qq.com/test.js"></script> <script> var val=123; </script>
【2】耦合问题
客户端编程的通用风格是保持HTML内容和javaScript行为分离,所以应该避免使用HTML事件处理程序属性,因为这些属性直接混合了javascript和HTML,且不易扩展
DOM0级事件处理程序
通过javascript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代Web浏览器中出现的,而且至今仍然为所有现代浏览器所支持。原因一是简单,二是具有跨浏览器的优势
每个元素都有自己的事件处理程序属性,这些属性通常全部小写,将这种属性的值设置为一个函数,就可以指定事件处理程序
[注意]以DOM0级方式添加的事件处理程序会在事件流的冒泡阶段被处理
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> box.onclick = function(){this.innerHTML += '1';} </script>
可以通过将事件处理程序属性设置为null来删除事件处理程序
box.onclick = null;
缺点
DOM0级事件处理程序的缺点是围绕着每个事件目标对于每种事件类型只能添加一个事件处理程序
DOM2级事件处理程序
DOM2级事件处理程序定义了两个方法用于处理指定和删除事件处理程序的操作:addEventListener()和removeEventListener()
所有DOM节点中都包含这两个方法,并且它们都接受3个参数:要处理的事件名、作为事件处理程序的函数和一个布尔值。最后的布尔值参数如果是true,表示在捕获阶段调用事件处理程序;如果是false,表示在冒泡阶段调用事件处理程序。若最后的布尔值不填写,则和false效果一样
[注意]IE8-浏览器不支持DOM2级事件处理程序
使用DOM2级事件处理程序的好处是可以添加多个事件处理程序,并按照他们添加的顺序触发
以下代码以1-2的顺序输出
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> box.addEventListener('click',function(){this.innerHTML += '1'},false); box.addEventListener('click',function(){this.innerHTML += '2'},false); </script>
以下代码以2-1的顺序输出
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> setTimeout(function(){ box.addEventListener('click',function(){this.innerHTML += '1'},false); },16); box.addEventListener('click',function(){this.innerHTML += '2'},false); </script>
参数
如果希望向监听函数传递参数,可以用匿名函数包装一下监听函数
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> box.addEventListener("click",function(){ test('123'); },false); function test(x){box.innerHTML += x;} </script>
移除
通过addEventListener()添加的事件处理程序只能使用removeEventListener()来移除,移除时传入的参数与添加处理程序时使用的参数相同。这意味着,addEventListener()添加的匿名函数将无法移除
以下无效
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> box.addEventListener("click",function(){ this.innerHTML += '1' },false); box.removeEventListener('click',function(){ this.innerHTML += '1' },false); </script>
以下有效
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> var handle = function(){this.innerHTML += '1'}; box.addEventListener("click",handle,false); box.removeEventListener('click',handle,false); </script>
IE事件处理程序
IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的两个参数:事件处理程序名称与事件处理程序函数。由于IE8-浏览器只支持事件冒泡,所以通过attachEvent()添加的事件处理程序都会被添加到事件冒泡阶段
attachEvent()方法的第一个参数是"onclick",而非DOM的addEventListener()方法中的"click"
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> box.attachEvent('onclick',function(){this.innerHTML += '1';}); </script>
[注意]attachEvent()方法只冒泡到document,且IE10-浏览器支持
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <button id="reset">还原</button> <script> //IE10-浏览器返回div body html document //其他浏览器报错 reset.onclick = function(){history.go();} box.attachEvent('onclick',function(){box.innerHTML += 'div\n';}); document.body.attachEvent('onclick',function(){box.innerHTML += 'body\n';}); document.documentElement.attachEvent('onclick',function(){box.innerHTML += 'html\n';}); document.attachEvent('onclick',function(){box.innerHTML += 'document\n';}); window.attachEvent('onclick',function(){box.innerHTML += 'window\n';}); </script>
this
与其他三个事件处理程序不同,IE事件处理程序的this指向window,而非被绑定事件的元素
<!-- <div> --> <div id="box" style="height:100px;width:300px;background-color:pink;" onclick = "console.log(this)"></div>
<div id="box" style="height:100px;width:300px;background-color:pink;"></div> <script> box.onclick= function(){ console.log(this);//<div> } </script>
<div id="box" style="height:100px;width:300px;background-color:pink;"></div> <script> box.addEventListener('click',function(){ console.log(this);//<div> }); </script>
<div id="box" style="height:100px;width:300px;background-color:pink;"></div> <script> box.attachEvent('onclick',function(){ console.log(this);//window }); </script>
顺序
使用attachEvent()方法添加的事件处理程序的触发顺序是有区别的。IE9、10浏览器是按正序执行的,而IE8-浏览器则是按倒序执行的
<div id="box" style="height:30px;width:100px;background-color:pink;"></div> <script> box.attachEvent('onclick',function(){ box.innerHTML += '1'; }); box.attachEvent('onclick',function(){ box.innerHTML += '2'; }); </script>
移除
使用attachEvent()添加的事件可以通过detachEvent()来移除,条件是必须提供相同的参数。与DOM方法一样,这也意味着添加的匿名函数将不能被移除。不过,只要能够将对相同函数的引用传给detachEvent(),就可以移除相应的事件处理程序
以下无效
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> box.attachEvent("onclick",function(){ box.innerHTML += '1' },false); box.detachEvent('onclick',function(){ box.innerHTML += '1' },false); </script>
以下有效
<div id="box" style="height:30px;width:200px;background-color:pink;"></div> <script> var handle = function(){box.innerHTML += '1'}; box.attachEvent("onclick",handle,false); box.detachEvent('onclick',handle,false); </script>
总结
由于IE8-浏览器不支持addEventListener()方法,所以需要配合attachEvent()方法来实现全浏览器的事件绑定兼容写法。同时,由于attachEvent()方法中的this指向window,所以需要对this进行显式修改
function addEvent(target,type,handler){
if(target.addEventListener){
target.addEventListener(type,handler,false);
}else{
target.attachEvent('on'+type,function(event){
return handler.call(target,event);
});
}
}
调用顺序
如果浏览器同时出现这四种事件处理程序,那么它们的调用顺序在各浏览器中表现并不一致
<div id="box" style="height:100px;width:100px;background:pink;" onclick = "this.innerHTML +='html\n'"></div> <script> if(box.addEventListener){ box.addEventListener('click',function(){this.innerHTML += 'DOM2级\n'}) } if(box.attachEvent){ box.attachEvent('onclick',function(){box.innerHTML +='IE\n'}) } box.onclick = function(){ this.innerHTML += 'DOM0级\n'; } </script>
【相同点】
如果同时出现HTML事件处理程序和DOM0级事件处理程序,DOM0级会覆盖HTML事件处理程序
【不同点】
chrome/opera/safari等webkit内核的浏览器会按照事件处理程序出现的顺序来排列,所以结果为:DOM2级 DOM0级
firefox浏览器和IE浏览器会将DOM0级事件优先调用
所以firefox和IE11浏览器结果为:DOM0级 DOM2级
IE9、10浏览器结果为:DOM0级 DOM2级 IE
IE8-浏览器结果为:DOM0级 IE
好的代码像粥一样,都是用时间熬出来的