发文不易,转载传播,请亲注明链接出处,谢谢!

内容提纲:

1.传统事件绑定的问题

2.W3C事件处理函数

3.IE事件处理函数

4.事件对象的其他内容

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

 

一.传统事件绑定的问题

传统事件绑定有内联模型和脚本模型,内联模型我们不做讨论,基本很少去用。先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。

  var box = document.getElementById('box');        //获取元素

        box.onclick = function () {                //元素点击触发事件

               alert('Lee');

        };

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

window.onload = function () {                           //第一组程序项目或第一个JS文件

alert('Lee');

};

window.onload = function () {                           //第二组程序项目或第二个JS文件

alert('Mr.Lee');

};

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

解决覆盖问题,我们可以这样去解决:

PS如果一开始没有window.onload,旧版火狐显示undefined,新版显示object,谷歌和IE浏览器也是object;如果有window.onload,所有浏览器都会显示function。

 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();                              //执行之前一个事件

        alert('Mr.Lee');                                 //执行本事件的代码

 };

问题二:事件切换器

window.onload = function () {

var box = document.getElementById('box');

box.onclick =toBlue;                    //第一次执行toBlue()

};

function toRed() {

this.className = 'red';

this.onclick = toBlue;                                 //第三次执行toBlue(),然后来回切换

}

PS:直接将函数绑定给事件处理函数,toBlue里面的this就是事件对象box,否则是window!(事件入门一篇提到过)

function toBlue() {

this.className = 'blue';

this.onclick = toRed;                                  //第二次执行toRed()

}

这个切换器在扩展的时候,会出现一些问题:

1.如果增加一个执行函数,那么会被覆盖

box.onclick = toAlert;                                       //被增加的函数

       box.onclick = toBlue;                                      //toAlert被toBlue覆盖了

2.如果解决覆盖问题,就必须包含到一起,然后同时执行,但又出新问题

box.onclick = function () {                                //包含进去,但可读性降低

//第一次不会被覆盖,但第二次又被覆盖(被toBlue里面的onclick覆盖)

     toAlert();

toBlue.call(this);                                      //还必须把this传递到切换器里

};

PS:通过匿名函数执行一个函数,此函数里面的this是window,所以必须在匿名函数中把this传递过去!

 

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

//添加事件函数

//obj相当于window

//type相当于onload

//fn相当于function () {}

//组合起来:window.onload = function(){}

 function addEvent(obj, type, fn) {                             //取代传统事件处理函数

        var saved = null;                                       //保存每次触发的事件处理函数

        if (typeof obj['on' + type] == 'function') {         //判断是不是事件

               saved = obj['on' + type];                        //如果有,保存起来

        }

        obj['on' + type] = function () {                  //然后执行window.onload相当于window['onload'];      

13         if (saved) saved();                               //执行上一个 

               fn.call(this);                               //执行函数,把this传递过去

        };

 }

 addEvent(window, 'load', function () {                //执行到了

        alert('Lee');

 });

 addEvent(window, 'load', function () {                //执行到了

        alert('Mr.Lee');

 });

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

addEvent(window, 'load', init);                          //注册第一次

addEvent(window, 'load', init);                          //注册第二次,应该忽略

function init() {

alert('Lee');

}

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

 addEvent(window, 'load', function () {

        var box = document.getElementById('box');

 addEvent(box, 'click', function () {       //增加一个执行函数,每次都执行,不会被覆盖

               alert('Mr.Lee');

        });

        addEvent(box, 'click', toBlue);

 });

 function toRed() {

        this.className = 'red';

        addEvent(this, 'click', toBlue);

 }

 function toBlue() {

        this.className = 'blue';

        addEvent(this, 'click', toRed);

 }

PS:当你单击很多很多次切换后,浏览器直接卡死,或者弹出一个错误:too much recursion(太多的递归)。主要的原因是,每次切换事件的时候,都保存下来,没有把无用的移除,导致越积越多,最后卡死(解决方案就是用完的事件及时的移除掉)。

//删除事件函数

 function removeEvent(obj, type) {

        if (obj['on'] + type) obj['on' + type] = null;         //删除事件处理函数

 }

以上的删除事件处理函数只不过是一刀切的删除了,这样虽然解决了卡死和太多递归的问题。但其他的事件处理函数也一并被删除了,导致最后得不到自己想要的结果。如果想要只删除指定的函数中的事件处理函数,那就需要遍历,查找。(这里就不做了,提示:在上面的删除函数中加上第三个参数fn)

 

 

 

二.W3C事件处理函数

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

//问题1:覆盖问题(解决)

 window.addEventListener('load', function () {

        alert('Lee');

 }, false);

 window.addEventListener('load', function () {

        alert('Mr.Lee');

 }, false);

//问题2:屏蔽掉相同函数问题(解决)

 window.addEventListener('load', init, false);        //第一次执行了

 window.addEventListener('load', init, false);        //第二次被屏蔽了

 function init() {

        alert('Lee');

 }

//问题3:是否传递了this

 window.addEventListener('load', function () {

        var box = document.getElementById('box');

        box.addEventListener('click', function(){

             alert(this);     //这儿的this是box

         }, false);

 },false);            

//修改事件切换器

 window.addEventListener('load', function () {

        var box = document.getElementById('box');

        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);

 }

//问题4:添加一个额外的方法会被覆盖或者只能执行一次(解决)

 window.addEventListener('load', function () {

        var box = document.getElementById('box');

         box.addEventListener('click', function () {          //不会被误删

             alert('Mr.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);

 }        

综上所述:W3C是比较完美的解决了这些问题,非常好用;但是IE8和之前的浏览器并不支持,而是采用了自己的事件,当然,IE9已经完全支持!

设置冒泡和捕获阶段

在事件入门一篇中介绍了事件冒泡,即从里到外触发。我们也可以通过event对象来阻止某一阶段的冒泡。

W3C现代事件绑定可以设置冒泡和捕获。把最后的布尔值设置成true,则为捕获;设置成false,则为冒泡

//设置为true,捕获

document.addEventListener('click', function () {

alert('document');

}, true);

  box.addEventListener('click', function () {

alert('div');

}, true);

 

三.IE事件处理函数

IE实现了与DOM中类似的两个方法:attachEvent()和detachEvent()。这两个方法接受相同的参数:事件名称和函数。

在使用这两组函数的时候,与W3C区别如下:

1.IE不支持捕获,只支持冒泡;

2.IE添加事件不能屏蔽重复的函数;

3.IE中的this指向的是window而不是DOM对象;

4.在传统事件上,IE是无法接受到event对象的,但使用了attchEvent()却可以,但有些区别。

//问题1:覆盖问题(解决,但有不同,输出顺序相反)

 window.attachEvent('onload', function () {

        alert('Lee');

 });

 window.attachEvent('onload', function () {

        alert('Mr.Lee');

 });

 window.attachEvent('onload', function () {

        alert('Miss.Lee');

 });

//问题2:相同函数屏蔽的问题(未解决)

 window.attachEvent('onload', init);

 window.attachEvent('onload', init);

 function init() {

        alert('Mr.Lee');

 }

//问题3:不可以传递this(未解决)

 window.attachEvent('onload', function () {

        var box = document.getElementById('box');

        box.attachEvent('onclick', function () {

               //alert(this === box);                   //false

               alert(this === window);                    //不能传递this

        });

 });

 //可以通过call()传递过去

 window.attachEvent('onload', function () {

        var box = document.getElementById('box');

        box.attachEvent('onclick', function () {       

               toBlue.call(box);                 //把this直接call过去(但切换器我们不这样做!)

        });

 });

//问题4:添加一个额外的方法会被覆盖或者只能执行一次(解决)

window.attachEvent('onload', function () {

       var box = document.getElementById('box');

       box.attachEvent('onclick', function () {

              alert('Lee');

       });

       box.attachEvent('onclick', function () {

              alert('Mr.Lee');

       });

});

在传统绑定上,IE是无法像W3C那样通过传参接受event对象,但如果使用了attachEvent()却可以。

 window.attachEvent('onload', function () {

        var box = document.getElementById('box');

        //box.onclick = function (evt) {                     //传统方法IE无法通过参数获取evt

        //     alert(evt);

        //}

        box.attachEvent('onclick', function (evt) {           //IE的现代事件绑定机制是可以的

               //alert(evt);                               //object

               //alert(evt.type);                                        //click    

               //alert(evt.srcElement.tagName);            //box, 这个可以有

               alert(window.event.srcElement.tagName);    //box, 这个更可以有

        });

 });

//IE事件切换器

 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);

 }

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

 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;

        }

 }

 addEvent(window, 'load', function () {

        var box = document.getElementById('box');

        addEvent(box, 'click', toBlue);

 });

 function toRed(evt) {

        var that = getTarget(evt);

        that.className = 'red';

        removeEvent(that, 'click', toRed);

        addEvent(that, 'click', toBlue);

 }

 function toBlue(evt) {

        var that = getTarget(evt);

        that.className = 'blue';

        removeEvent(that, 'click', toBlue);

        addEvent(that, 'click', toRed);

 }

PS:调用忽略,IE兼容的事件,如果要传递this,改成call即可(上面问题3部分说过了,但是一般不使用这种形式)。

PS:IE中的事件绑定函数attachEvent()和detachEvent()可能在实践中不去使用,有几个原因:

1.IE9就将全面支持W3C中的事件绑定函数;

2.IE的事件绑定函数无法传递this;

3.IE的事件绑定函数不支持捕获;

4.同一个函数注册绑定后,没有屏蔽掉;

5.有内存泄漏的问题。

至于怎么替代,这儿暂时不做探讨···

 

四.事件对象的其他内容

1.获取移入移出对象

W3C提供了一个属性:relatedTarget;这个属性可以在mouseover和mouseout事件中获取从哪里移入和从哪里移出的DOM对象。

box.onmouseover = function (evt) {                     //鼠标移入box

alert(evt.relatedTarget);                              //获取移入box最近的那个元素对象

}

  box.onmouseout = function (evt) {                         //鼠标移出box

alert(evt.relatedTarget);                              //获取移出box最近的那个元素对象

}

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

box.onmouseover = function (evt) {                   //鼠标移入box

alert(window.event.fromElement.tagName); //获取移入box最近的那个元素对象

}

box.onmouseout = function (evt) {                            //鼠标移入box

alert(window.event.toElement.tagName);     //获取移入box最近的那个元素对象

}

跨浏览器兼容:

 function getTarget(evt) {

        var e = evt || window.event;                        //得到事件对象

        if (e.srcElement) {                                    //如果支持srcElement,表示IE

               if (e.type == 'mouseover') {                   //如果是mouseover

                      return e.fromElement;                   //就使用fromElement

               } else if (e.type == 'mouseout') {              //如果是mouseout

                      return e.toElement;                      //就使用toElement

               }

        } else if (e.relatedTarget) {                           //如果支持relatedTarget,表示W3C

               return e.relatedTarget;

        }

 }

2.阻止默认行为

有时我们需要阻止事件的默认行为,比如:一个超链接的默认行为就点击然后跳转到指定的页面。那么阻止默认行为就可以屏蔽跳转的这种操作,而实现自定义操作。

取消事件默认行为还有一种不规范的做法,就是返回false。

link.onclick = function () {

alert('Lee');

      return false;                                              //直接给个false,就不会跳转了。

};

PS:虽然return false;可以实现这个功能,但有漏洞:

第一:必须写到最后,这样导致中间的代码执行后,有可能执行不到return false;

第二:return false写到最前那么之后的自定义操作就失效了。

所以,最好的方法应该是在最前面就阻止默认行为,并且后面还能执行代码。

   link.onclick = function (evt) {

               evt.preventDefault();                               //W3C阻止默认行为,放哪里都可以

               alert('Mr.Lee');

        };

        link.onclick = function (evt) {

13               window.event.returnValue = false;                  //IE阻止默认行为

               alert('Mr.Lee');

        };

跨浏览器兼容:

 function preDef(evt) {

        var e = evt || window.event;

        if (e.preventDefault) {

               e.preventDefault();

        } else {

               e.returnValue = false;

        }

 } 

3.上下文菜单事件

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

小示例:

html代码部分:

 <body>

 <textarea id="text" style="width:200px;height:100px;"></textarea>

 <ul id="menu">

               <li>菜单1</li>

               <li>菜单2</li>

               <li>菜单3</li>

 </ul>

 </body>

css代码部分:

 #menu {

        width:50px;

        background:grey;

        position:absolute;

        display:none;

 }

JS代码部分:

addEvent(window, 'load', function () {

       var text = document.getElementById('text');

       addEvent(text, 'contextmenu', function (evt) {

              preDef(evt);

              var menu = document.getElementById('menu');

              var e = evt || window.event;

              menu.style.left = e.clientX + 'px';

              menu.style.top = e.clientY + 'px';

              menu.style.display = 'block';

              addEvent(document, 'click', function () {

                     menu.style.display = 'none';

              });

       });

});

PS:contextmenu事件很常用,而且此事件各浏览器兼容性较为稳定。

4.卸载前事件

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

addEvent(window, 'beforeunload', function (evt) {

preDef(evt);    //必须要有,默认形式

});

5.鼠标滚轮事件

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

 addEvent(document, 'mousewheel', function (evt) {          //非火狐

        alert(getWD(evt));

 });

 addEvent(document, 'DOMMouseScroll', function (evt) {  //火狐

        alert(getWD(evt));

 });

 function getWD(evt) {

        var e = evt || window.event;

        if (e.wheelDelta) {

               return e.wheelDelta;

        } else if (e.detail) {

               return -evt.detail * 30;                        //保持计算的统一

        }

 }

PS:通过浏览器检测可以确定火狐只执行DOMMouseScroll。

6.其他

DOMContentLoaded事件和readystatechange事件(很重要),有关DOM加载方面的事件,关于这两个事件的内容非常多,这儿就暂时不聊了!

for my lover and

thank you Mr.Lee!

JavaScript事件---事件绑定和深入的更多相关文章

  1. javascript对象事件绑定方法

    javascript对象事件绑定方法 今天在做对象事件绑定的过程中出现了一点异外情况,由于事件方法是由参数传过来的,需要将当前对象call过去,方便方法体里直接调用this 错误写法 obj.oncl ...

  2. javascript之事件绑定

    曾经写过一篇随笔,attachEvent和addEventListener,跟本文内容有很多相似之处 本文链接:javascript之事件绑定 1.原始写法 <div onclick=" ...

  3. JavaScript事件属性绑定带参数的函数

    JavaScript中在对事件进行绑定的时候,往往是element.onclick=event;这种形式,这样使用的话则会出现无法传参数.因此我们可以使用function(){}匿名函数将事件包含其中 ...

  4. jQuery 事件绑定 和 JavaScript 原生事件绑定

    总结一下:jQuery 事件绑定 和 JavaScript 原生事件绑定 及 区别 jQuery 事件绑定 jQuery 中提供了四种事件监听绑定方式,分别是 bind.live.delegate.o ...

  5. 解密jQuery事件核心 - 绑定设计(一)

    说起jQuery的事件,不得不提一下Dean Edwards大神 addEvent库,很多流行的类库的基本思想从他那儿借来的 jQuery的事件处理机制吸取了JavaScript专家Dean Edwa ...

  6. jQuery $(document).ready()和JavaScript onload事件

    jQuery $(document).ready()和JavaScript onload事件 Why we need a right time? 对元素的操作和事件的绑定需要等待一个合适的时机,可以看 ...

  7. Javascript事件模型系列(四)我所理解的javascript自定义事件

    被我拖延了将近一个月的javascript事件模型系列终于迎来了第四篇,也是我计划中的最后一篇,说来太惭愧了,本来计划一到两个星期写完的,谁知中间遇到了很多事情,公司的个人的,搞的自己心烦意乱浮躁了一 ...

  8. jquery事件重复绑定解决办法

    一$.fn.live 重复绑定 解决:使用die()方法,在live()方法绑定前,将此元素上的前面被绑定的事件统统解除,然后再通过live()方法绑定新的事件. //先通过die()方法解除,再通过 ...

  9. javascript的事件

    前戏 今天在博客中看到了javascript的事件机制,就自己试试写一个简单的冒泡捕获测试,但是测试结果出乎了我的意料,主要是自己原来对事件了解不是很清楚,现在写篇博客记录下. 基础 先来看一下我在A ...

  10. JavaScript的事件代理(转)

    如果你想给网页添加点JavaScript的交互性,也许你已经听过JavaScript的事件代理(event delegation),并且觉得这是那些发烧友级别的JavaScript程序员才会关心的什么 ...

随机推荐

  1. EditText 显示明文和密码

    1.效果图 2.布局文件 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xml ...

  2. Pop Easy – 轻松实现模态窗口的 jQuery 插件

    PopEasy 是一款轻量的 jQuery 插件,可以帮助开发人员容易的创建效果精美的模态窗口.PopEasy 在主流浏览器中都能够正常工作,同时兼容 IE 7 哦. 您可能感兴趣的相关文章 Metr ...

  3. awk 筛选特定长度的序列

    awk '/^>/ {printf("\n%s\t",$0);next;} {printf("%s",$0);} END {printf("\n ...

  4. NOIP 2013 货车运输【Kruskal + 树链剖分 + 线段树 】【倍增】

    NOIP 2013 货车运输[树链剖分] 树链剖分 题目描述 Description A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路.每一条道路对车辆都有重量限制,简称限重.现在 ...

  5. 用友华表Cell一些用法小结(cs.net版本)

    //从Color类型得到RGB类型,也可以用ColorTranslator.ToOle()方法 public int GetRGBFromColor(Color color) { byte r = c ...

  6. hdu 5876 ACM/ICPC Dalian Online 1009 Sparse Graph

    题目链接 分析:这叫补图上的BFS,萌新第一次遇到= =.方法很简单,看了别人的代码后,自己也学会了.方法就是开两个集合,一个A表示在下一次bfs中能够到达的点,另一个B就是下一次bfs中到不了的点. ...

  7. zookeeper 用法和日常运维

    本文以ZooKeeper3.4.3版本的官方指南为基础:http://zookeeper.apache.org/doc/r3.4.3/zookeeperAdmin.html,补充一些作者运维实践中的要 ...

  8. final specifier (since C++11)

    Specifies that a virtual function cannot be overridden in a derived class or that a class cannot be  ...

  9. 用户 &#39;NT AUTHORITY\NETWORK SERVICE&#39; 登录失败/OLE DB 错误: OLE DB 或 ODBC 错误 :

    用户 'NT AUTHORITY\NETWORK SERVICE' 登录失败/OLE DB 错误: OLE DB 或 ODBC 错误 : 2012-2-23 上午 ,弄SAAS时,发现在生成多维数据集 ...

  10. 用连接池提高Servlet访问数据库的效率

    Java Servlet作为首选的服务器端数据处理技术,正在迅速取代CGI脚本.Servlet超越CGI的优势之一在于,不仅多个请求可以共享公用资源,而且还可以在不同用户请求之间保留持续数据.本文介绍 ...