JavaScript的事件绑定及深刻

事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定
(DOM2 级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。windows


一.传统事件绑定的问题
传统事件绑定有内联模型和脚本模型,内联模型咱们不作讨论,基本不多去用。先来看
一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。浏览器

  
  
           
  
  
  1. var box = document.getElementById('box'); //获取元素 
  2. box.onclick = function () { //元素点击触发事件 
  3. alert('Lee'); 
  4. }; 

问题一:一个事件处理函数触发两次事件ide

  
  
           
  
  
  1. window.onload = function () { //第一组程序项目或第一个JS 文件 
  2. alert('Lee'); 
  3. }; 
  4.  
  5. window.onload = function () { //第二组程序项目或第二个JS 文件 
  6. alert('Mr.Lee'); 
  7. }; 

当两组程序或两个JS 文件同时执行的时候,后面一个会把前面一个彻底覆盖掉。致使
前面的window.onload 彻底失效了。函数


解决覆盖问题,咱们能够这样去解决:this

  
  
           
  
  
  1. window.onload = function () { //第一个要执行的事件,会被覆盖 
  2. alert('Lee'); 
  3. }; 
  4. if (typeof window.onload == 'function') { //判断以前是否有window.onload 
  5. var saved = null//建立一个保存器 
  6. saved = window.onload; //把以前的window.onload 保存起来 
  7. window.onload = function () { //最终一个要执行事件 
  8. if (saved) saved(); //执行以前一个事件 
  9. alert('Mr.Lee'); //执行本事件的代码 
  10. }; 

问题二:事件切换器spa

  
  
           
  
  
  1. box.onclick = toBlue; //第一次执行boBlue() 
  2. function toRed() { 
  3. this.className = 'red'
  4. this.onclick = toBlue; //第三次执行toBlue(),而后来回切换 
  5. function toBlue() { 
  6. this.className = 'blue'
  7. this.onclick = toRed; //第二次执行toRed() 

这个切换器在扩展的时候,会出现一些问题:
1.若是增长一个执行函数,那么会被覆盖对象

  
  
           
  
  
  1. box.onclick = toAlert; //被增长的函数 
  2. box.onclick = toBlue; //toAlert 被覆盖了 

2.若是解决覆盖问题,就必须包含同时执行,但又出新问题递归

  
  
           
  
  
  1. box.onclick = function () { //包含进去,但可读性下降 
  2. toAlert(); //第一次不会被覆盖,但第二次又被覆盖 
  3. toBlue.call(this); //还必须把this 传递到切换器里 
  4. }; 

综上的三个问题:覆盖问题、可读性问题、this 传递问题。咱们来建立一个自定义的事
件处理函数,来解决以上三个问题。seo

  
  
           
  
  
  1. function addEvent(obj, type, fn) { //取代传统事件处理函数 
  2. var saved = null//保存每次触发的事件处理函数 
  3. if (typeof obj['on' + type] == 'function') { //判断是否是事件 
  4. saved = obj['on' + type]; //若是有,保存起来 
  5. obj['on' + type] = function () { //而后执行 
  6. if (saved) saved(); //执行上一个 
  7. fn.call(this); //执行函数,把this 传递过去 
  8. }; 
  9. addEvent(window, 'load'function () { //执行到了 
  10. alert('Lee'); 
  11. }); 
  12. addEvent(window, 'load'function () { //执行到了 
  13. alert('Mr.Lee'); 
  14. }); 

PS:以上编写的自定义事件处理函数,还有一个问题没有处理,就是两个相同函数名
的函数误注册了两次或屡次,那么应该把多余的屏蔽掉。那,咱们就须要把事件处理函数进
行遍历,若是有一样名称的函数名就不添加便可。(这里就不作了)事件

  
  
           
  
  
  1. addEvent(window, 'load', init); //注册第一次 
  2. addEvent(window, 'load', init); //注册第二次,应该忽略 
  3. function init() { 
  4. alert('Lee'); 

用自定义事件函数注册到切换器上查看效果:

  
  
           
  
  
  1. addEvent(window, 'load'function () { 
  2. var box = document.getElementById('box'); 
  3. addEvent(box, 'click', toBlue); 
  4. }); 
  5. function toRed() { 
  6. this.className = 'red'
  7. addEvent(this'click', toBlue); 
  8. function toBlue() { 
  9. this.className = 'blue'
  10. addEvent(this'click', toRed); 

PS:当你单击不少不少次切换后,浏览器直接卡死,或者弹出一个错误:too much
recursion(太多的递归)。主要的缘由是,每次切换事件的时候,都保存下来,没有把无用的
移除,致使越积越多,最后卡死。

  
  
           
  
  
  1. function removeEvent(obj, type) { 
  2. if (obj['on'] + type) obj['on' + type] = null//删除事件处理函数 

以上的删除事件处理函数只不过是一刀切的删除了,这样虽然解决了卡死和太多递归的
问题。但其余的事件处理函数也一并被删除了,致使最后得不到本身想要的结果。若是想要
只删除指定的函数中的事件处理函数,那就须要遍历,查找。(这里就不作了)


二.W3C事件处理函数
“DOM2 级事件”定义了两个方法,用于添加事件和删除事件处理程序的操做:
addEventListener()和removeEventListener()。全部DOM 节点中都包含这两个方法,而且它
们都接受3 个参数;事件名、函数、冒泡或捕获的布尔值(true 表示捕获,false 表示冒泡)。

  
  
           
  
  
  1. window.addEventListener('load'function () { 
  2. alert('Lee'); 
  3. }, false); 
  4. window.addEventListener('load'function () { 
  5. alert('Mr.Lee'); 
  6. }, false); 

PS:W3C 的现代事件绑定比咱们自定义的好处就是:1.不须要自定义了;2.能够屏蔽相
同的函数;3.能够设置冒泡和捕获。

  
  
           
  
  
  1. window.addEventListener('load', init, false); //第一次执行了 
  2. window.addEventListener('load', init, false); //第二次被屏蔽了 
  3. function init() { 
  4. alert('Lee'); 

事件切换器

  
  
           
  
  
  1. window.addEventListener('load'function () { 
  2. var box = document.getElementById('box'); 
  3. box.addEventListener('click'function () { //不会被误删 
  4. alert('Lee'); 
  5. }, false); 
  6. box.addEventListener('click', toBlue, false); //引入切换也不会太多递归卡死 
  7. }, false); 
  8. function toRed() { 
  9. this.className = 'red'
  10. this.removeEventListener('click', toRed, false); 
  11. this.addEventListener('click', toBlue, false); 
  12. function toBlue() { 
  13. this.className = 'blue'
  14. this.removeEventListener('click', toBlue, false); 
  15. this.addEventListener('click', toRed, false); 

设置冒泡和捕获阶段
以前咱们上一章了解了事件冒泡,即从里到外触发。咱们也能够经过event 对象来阻止
某一阶段的冒泡。那么W3C 现代事件绑定能够设置冒泡和捕获。

  
  
           
  
  
  1. document.addEventListener('click'function () { 
  2. alert('document'); 
  3. }, true); //把布尔值设置成true,则为捕获 
  4. box.addEventListener('click'function () { 
  5. alert('Lee'); 
  6. }, true); //把布尔值设置成false,则为冒泡 

三.IE事件处理函数
IE 实现了与DOM 中相似的两个方法:attachEvent()和detachEvent()。这两个方法接受
相同的参数:事件名称和函数。
在使用这两组函数的时候,先把区别说一下:1.IE 不支持捕获,只支持冒泡;2.IE 添加
事件不能屏蔽重复的函数;3.IE 中的this 指向的是window 而不是DOM 对象。4.在传统事
件上,IE 是没法接受到event 对象的,但使用了attchEvent()却能够,但有些区别。

  
  
           
  
  
  1. window.attachEvent('onload'function () { 
  2. var box = document.getElementById('box'); 
  3. box.attachEvent('onclick', toBlue); 
  4. }); 
  5. function toRed() { 
  6. var that = window.event.srcElement; 
  7. that.className = 'red'
  8. that.detachEvent('onclick', toRed); 
  9. that.attachEvent('onclick', toBlue); 
  10. function toBlue() { 
  11. var that = window.event.srcElement; 
  12. that.className = 'blue'
  13. that.detachEvent('onclick', toBlue); 
  14. that.attachEvent('onclick', toRed); 

PS:IE 不支持捕获,无解。IE 不能屏蔽,须要单独扩展或者自定义事件处理。IE 不能
传递this,能够call 过去。

  
  
           
  
  
  1. window.attachEvent('onload'function () { 
  2. var box = document.getElementById('box'); 
  3. box.attachEvent('onclick'function () { 
  4. alert(this === window); //this 指向的window 
  5. }); 
  6. }); 
  7. window.attachEvent('onload'function () { 
  8. var box = document.getElementById('box'); 
  9. box.attachEvent('onclick'function () { 
  10. toBlue.call(box); //把this 直接call 过去 
  11. }); 
  12. }); 
  13. function toThis() { 
  14. alert(this.tagName); 

在传统绑定上,IE 是没法像W3C 那样经过传参接受event 对象,但若是使用了
attachEvent()却能够。

  
  
           
  
  
  1. box.onclick = function (evt) { 
  2. alert(evt); //undefined 
  3. box.attachEvent('onclick'function (evt) { 
  4. alert(evt); //object 
  5. alert(evt.type); //click 
  6. }); 
  7. box.attachEvent('onclick'function (evt) { 
  8. alert(evt.srcElement === box); //true 
  9. alert(window.event.srcElement === box); //true 
  10. }); 

最后,为了让IE 和W3C 能够兼容这个事件切换器,咱们能够写成以下方式:

  
  
           
  
  
  1. function addEvent(obj, type, fn) { //添加事件兼容 
  2. if (obj.addEventListener) { 
  3. obj.addEventListener(type, fn); 
  4. else if (obj.attachEvent) { 
  5. obj.attachEvent('on' + type, fn); 
  6. function removeEvent(obj, type, fn) { //移除事件兼容 
  7. if (obj.removeEventListener) { 
  8. obj.removeEventListener(type, fn); 
  9. else if (obj.detachEvent) { 
  10. obj.detachEvent('on' + type, fn); 
  11. function getTarget(evt) { //获得事件目标 
  12. if (evt.target) { 
  13. return evt.target; 
  14. else if (window.event.srcElement) { 
  15. return window.event.srcElement; 

PS:调用忽略,IE 兼容的事件,若是要传递this,改为call 便可。
PS:IE 中的事件绑定函数attachEvent()和detachEvent()可能在实践中不去使用,有几个
缘由:1.IE9 就将全面支持W3C 中的事件绑定函数;2.IE 的事件绑定函数没法传递this;3.IE
的事件绑定函数不支持捕获;4.同一个函数注册绑定后,没有屏蔽掉;5.有内存泄漏的问题。
至于怎么替代,咱们将在之后的项目课程中探讨。


四.事件对象的其余补充
在W3C 提供了一个属性:relatedTarget;这个属性能够在mouseover 和mouseout 事件
中获取从哪里移入和从哪里移出的DOM 对象。

  
  
           
  
  
  1. box.onmouseover = function (evt) { //鼠标移入box 
  2. alert(evt.relatedTarget); //获取移入box 最近的那个元素对象 
  3. //span 
  4. box.onmouseout = function (evt) { //鼠标移出box 
  5. alert(evt.relatedTarget); //获取移出box 最近的那个元素对象 
  6. //span 

IE 提供了两组分别用于移入移出的属性:fromElement 和toElement,分别对应mouseover
和mouseout。

  
  
           
  
  
  1. box.onmouseover = function (evt) { //鼠标移入box 
  2. alert(window.event.fromElement.tagName); //获取移入box 最近的那个元素对象span 
  3. box.onmouseout = function (evt) { //鼠标移入box 
  4. alert(window.event.toElement.tagName); //获取移入box 最近的那个元素对象span 

PS:fromElement 和toElement 若是分别对应相反的鼠标事件,没有任何意义。


剩下要作的就是跨浏览器兼容操做:

  
  
           
  
  
  1. function getTarget(evt) { 
  2. var e = evt || window.event; //获得事件对象 
  3. if (e.srcElement) { //若是支持srcElement,表示IE 
  4. if (e.type == 'mouseover') { //若是是over 
  5. return e.fromElement; //就使用from 
  6. else if (e.type == 'mouseout') { //若是是out 
  7. return e.toElement; //就使用to 
  8. else if (e.relatedTarget) { //若是支持relatedTarget,表示W3C 
  9. return e.relatedTarget; 

有时咱们须要阻止事件的默认行为,好比:一个超连接的默认行为就点击而后跳转到指
定的页面。那么阻止默认行为就能够屏蔽跳转的这种操做,而实现自定义操做。
取消事件默认行为还有一种不规范的作法,就是返回false。

  
  
           
  
  
  1. link.onclick = function () { 
  2. alert('Lee'); 
  3. return false//直接给个假,就不会跳转了。 
  4. }; 

PS:虽然return false;能够实现这个功能,但有漏洞;第一:必须写到最后,这样致使
中间的代码执行后,有可能执行不到return false;第二:return false 写到最前那么以后的自
定义操做就失效了。因此,最好的方法应该是在最前面就阻止默认行为,而且后面还能执行
代码。

  
  
           
  
  
  1. link.onclick = function (evt) { 
  2. evt.preventDefault(); //W3C,阻止默认行为,放哪里均可以 
  3. alert('Lee'); 
  4. }; 
  5. link.onclick = function (evt) { //IE,阻止默认行为 
  6. window.event.returnValue = false
  7. alert('Lee'); 
  8. }; 

跨浏览器兼容

  
  
           
  
  
  1. function preDef(evt) { 
  2. var e = evt || window.event; 
  3. if (e.preventDefault) { 
  4. e.preventDefault(); 
  5. else { 
  6. e.returnValue = false

上下文菜单事件:contextmenu,当咱们右击网页的时候,会自动出现windows 自带的
菜单。那么咱们可使用contextmenu 事件来修改咱们指定的菜单,但前提是把右击的默认
行为取消掉。

  
  
           
  
  
  1. addEvent(window, 'load'function () { 
  2. var text = document.getElementById('text'); 
  3. addEvent(text, 'contextmenu'function (evt) { 
  4. var e = evt || window.event; 
  5. preDef(e); 
  6. var menu = document.getElementById('menu'); 
  7. menu.style.left = e.clientX + 'px'
  8. menu.style.top = e.clientY + 'px'
  9. menu.style.visibility = 'visible'
  10. addEvent(document, 'click'function () { 
  11. document.getElementById('myMenu').style.visibility = 'hidden'
  12. }); 
  13. }); 
  14. }); 

PS:contextmenu 事件很经常使用,这直接致使浏览器兼容性较为稳定。


卸载前事件:beforeunload,这个事件能够帮助在离开本页的时候给出相应的提示,“离
开”或者“返回”操做。

  
  
           
  
  
  1. addEvent(window, 'beforeunload'function (evt) { 
  2. preDef(evt); 
  3. }); 

鼠标滚轮(mousewheel)和DOMMouseScroll,用于获取鼠标上下滚轮的距离。

  
  
           
  
  
  1. addEvent(document, 'mousewheel'function (evt) { //非火狐 
  2. alert(getWD(evt)); 
  3. }); 
  4. addEvent(document, 'DOMMouseScroll'function (evt) { //火狐 
  5. alert(getWD(evt)); 
  6. }); 
  7. function getWD(evt) { 
  8. var e = evt || window.event; 
  9. if (e.wheelDelta) { 
  10. return e.wheelDelta; 
  11. else if (e.detail) { 
  12. return -evt.detail * 30; //保持计算的统一 

PS:经过浏览器检测能够肯定火狐只执行DOMMouseScroll。

DOMContentLoaded 事件和readystatechange 事件,有关DOM 加载方面的事件,关于这 两个事件的内容很是多且繁杂,咱们先点明在这里,在项目课程中使用的时候详细讨论。

相关文章
相关标签/搜索