事件绑定与深入详解
事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2 级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。
一.传统事件绑定的问题
传统事件绑定有内联模型和脚本模型,内联模型我们不做讨论,基本很少去用。先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。
<script type="text/javascript"> window.onload = function(){ var box = document.getElementById('box'); //获取元素 box.onclick = function () { //元素点击触发事件 alert('Lee'); }; }; </script> </head> <body> <div id="box"> 测试DIV </div> </body>
问题一:一个事件处理函数触发两次事件
<script type="text/javascript"> window.onload = function () { //第一组程序项目或第一个 JS 文件 alert('Lee'); }; window.onload = function () { //第二组程序项目或第二个 JS 文件 alert('Mr.Lee'); }; //如果页面有两个或者多个js,并且第一个js是第一个程序员开发的,第二个js是第二个程序员开发的 </script> </head> <body> <div id="box"> 测试DIV </div> </body>
当两组程序或两个 JS 文件同时执行的时候,后面一个会把前面一个完全覆盖掉。导致前面的 window.onload 完全失效了。解决覆盖问题,我们可以这样去解决:
<script type="text/javascript"> window.onload = function () { //第一个要执行的事件,会被覆盖 alert('Lee'); }; if (typeof window.onload == 'function') { //判断之前是否有 window.onload var saved = null; //创建一个保存器 saved = window.onload; //把之前的 window.onload 保存起来 } window.onload = function () { //最终一个要执行事件 if (saved)
saved(); //执行之前一个事件,saved就是window.onload.saved()相当于window.onload(),但window.onload()是不能执行的,所以saved()相当于window.onload=function(){} alert('Mr.Lee'); //执行本事件的代码 }; </script> </head> <body> <div id="box"> 测试DIV </div> </body>
问题二:事件切换器
<script type="text/javascript"> window.onload=function(){ var box = document.getElementById("box"); box.onclick=toBlue; } function toRed() { this.className = 'red'; this.onclick = toBlue; //第三次执行 toBlue(),然后来回切换 } function toBlue() { this.className = 'blue'; this.onclick = toRed; //第二次执行 toRed() } </script> <style type="text/css"> .red{ width:100px; height:100px; background:red; } .blue{ background:blue; width:100px; height:100px; } </style> </head> <body> <div id="box" class="red"> 测试DIV </div> </body>
这个切换器在扩展的时候,会出现一些问题:如果增加一个执行函数,那么会被覆盖
<script type="text/javascript"> window.onload=function(){ var box = document.getElementById("box"); box.onclick=function(){ //被下面的覆盖了,无法执行 alert("lee"); } box.onclick=toBlue; } function toRed() { this.className = 'red'; this.onclick = toBlue; //第三次执行 toBlue(),然后来回切换 } function toBlue() { this.className = 'blue'; this.onclick = toRed; //第二次执行 toRed() } </script> <style type="text/css"> .red{ width:100px; height:100px; background:red; } .blue{ background:blue; width:100px; height:100px; } </style> </head> <body> <div id="box" class="red"> 测试DIV </div> </body>
如果解决覆盖问题,就必须包含同时执行,但又出新问题(可读性变差,this传递)(解决这些问题要使用事件处理函数)
<script type="text/javascript"> window.onload=function(){ var box = document.getElementById("box"); box.onclick = function () { //包含进去,但可读性降低 toAlert(); //第一次不会被覆盖,但第二次又被覆盖 toBlue.call(this); //还必须把 this 传递到切换器里 }; } function toAlert(){ alert("Lee"); } function toRed() { this.className = 'red'; this.onclick = toBlue; //第三次执行 toBlue(),然后来回切换 } function toBlue() { this.className = 'blue'; this.onclick = toRed; //第二次执行 toRed() } </script> <style type="text/css"> .red{ width:100px; height:100px; background:red; } .blue{ background:blue; width:100px; height:100px; } </style> </head> <body> <div id="box" class="red"> 测试DIV </div> </body>
二. W3C事件处理函数
“DOM2 级事件”定义了两个方法,用于添加事件和删除事件处理程序的操作:addEventListener()和 removeEventListener()。
所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数;事件名、函数、冒泡或捕获的布尔值(true 表示捕获,false 表示冒泡(IE6,7,8只支持冒泡,所以要兼容只能用冒泡))。
<script type="text/javascript"> window.addEventListener('load', function () { alert('Lee'); }, false); window.addEventListener('load', function () { alert('Mr.Lee'); }, false); //解决的覆盖的问题 </script> <style type="text/css"> .red{ width:100px; height:100px; background:red; } .blue{ background:blue; width:100px; height:100px; } </style> </head> <body> <div id="box" class="red"> 测试DIV </div> </body>
W3C 的现代事件绑定比我们自定义的好处就是:1.不需要自定义了;2.可以屏蔽相同的函数;3.可以设置冒泡和捕获。
<script type="text/javascript"> window.addEventListener('load', init, false); //第一次执行了 window.addEventListener('load', init, false); //第二次被屏蔽了 function init() { alert('Lee'); } </script>
<script type="text/javascript"> //事件切换器 window.addEventListener('load', function () { var box = document.getElementById('box'); box.addEventListener('click', function () { //不会被误删 alert('Lee'); }, false); box.addEventListener('click', toBlue, false); //引入切换也不会太多递归卡死 }, false); function toRed() { this.className = 'red'; this.removeEventListener('click', toRed, false); this.addEventListener('click', toBlue, false); } function toBlue() { this.className = 'blue'; this.removeEventListener('click', toBlue, false); this.addEventListener('click', toRed, false); } </script> <style type="text/css"> .red{ width:100px; height:100px; background:red; } .blue{ background:blue; width:100px; height:100px; } </style> </head> <body> <div id="box" class="red"> 测试DIV </div> </body>
综上所述,W3C完美解决了了这些问题,比较好用,但是IE6,7,8不支持,而是采用了自己的事件,当然IE9及以上已经支持了
三. IE事件处理函数
IE 实现了与 DOM 中类似的两个方法:attachEvent()和 detachEvent()。这两个方法接受相同的参数:事件名称和函数。
在使用这两组函数的时候,先把区别说一下:1.IE 不支持捕获,只支持冒泡;2.IE 添加事件不能屏蔽重复的函数;3.IE 中的 this 指向的是 window 而不是 DOM 对象。4.在传统事件上,IE 是无法接受到 event 对象的,但使用了 attchEvent()却可以,但有些区别。
<script type="text/javascript"> window.attachEvent('onload', function () { var box = document.getElementById('box'); box.attachEvent('onclick', toBlue); }); function toRed() { var that = window.event.srcElement; that.className = 'red'; that.detachEvent('onclick', toRed); that.attachEvent('onclick', toBlue); } function toBlue() { var that = window.event.srcElement; that.className = 'blue'; that.detachEvent('onclick', toBlue); that.attachEvent('onclick', toRed); } </script> <style type="text/css"> .red{ width:100px; height:100px; background:red; } .blue{ background:blue; width:100px; height:100px; } </style> </head> <body> <div id="box" class="red"> 测试DIV </div> </body>
IE 不支持捕获,无解。IE 不能屏蔽,需要单独扩展或者自定义事件处理。IE 不能传递 this,可以 call 过去。
<script type="text/javascript"> window.attachEvent('onload', function () { var box = document.getElementById('box'); box.attachEvent('onclick', function () { alert(this === window); //this 指向的 window }); }); window.attachEvent('onload', function () { var box = document.getElementById('box');box.attachEvent('onclick', function () { toBlue.call(box); //把 this 直接 call 过去 }); }); function toThis() { alert(this.tagName); } </script> <style type="text/css"> .red{ width:100px; height:100px; background:red; } .blue{ background:blue; width:100px; height:100px; } </style> </head> <body> <div id="box" class="red"> 测试DIV </div> </body>
在传统绑定上,IE 是无法像 W3C 那样通过传参接受 event 对象,但如果使用了attachEvent()却可以。
<script type="text/javascript"> box.onclick = function (evt) { alert(evt); //undefined } box.attachEvent('onclick', function (evt) { alert(evt); //object alert(evt.type); //click }); box.attachEvent('onclick', function (evt) { alert(evt.srcElement === box); //true alert(window.event.srcElement === box); //true }); </script>
最后,为了让 IE 和 W3C 可以兼容这个事件切换器,我们可以写成如下方式:
<script type="text/javascript"> function addEvent(obj, type, fn) { //添加事件兼容 if (obj.addEventListener) { obj.addEventListener(type, fn); } else if (obj.attachEvent) { obj.attachEvent('on' + type, fn); } } function removeEvent(obj, type, fn) { //移除事件兼容 if (obj.removeEventListener) { obj.removeEventListener(type, fn); } else if (obj.detachEvent) { obj.detachEvent('on' + type, fn); } } function getTarget(evt) { //得到事件目标 if (evt.target) { return evt.target; } else if (window.event.srcElement) { return window.event.srcElement; } } </script>
PS:调用忽略,IE 兼容的事件,如果要传递 this,改成 call 即可。
PS:IE 中的事件绑定函数 attachEvent()和 detachEvent()可能在实践中不去使用,有几个原因:
1.IE9 就将全面支持 W3C 中的事件绑定函数;
2.IE 的事件绑定函数无法传递 this;
3.IE的事件绑定函数不支持捕获;
4.同一个函数注册绑定后,没有屏蔽掉;5.有内存泄漏的问题。
四.事件对象的其他补充
在 W3C 提供了一个属性:relatedTarget;这个属性可以在 mouseover 和 mouseout 事件中获取从哪里移入和从哪里移出的 DOM 对象。