zoukankan      html  css  js  c++  java
  • javascript事件机制详解

    什么是事件?

      直观的说就是网页上发生的事情,按照我的归纳来说就是:动作、变化、加载完成。具体来说就是鼠标点击某些内容,经过某些元素,键盘按下按键,web页面加载完成,以及浏览器窗口大小变化,滚动条滚动......

      事件是构成动态网页交互必不可少的条件,通过使用 JavaScript ,你可以监听特定事件的发生,并规定让某些事件发生以对这些事件做出响应。

    javascript的三种事件模型(原始,IE,DOM2)

    1.原始事件模型  

      其事件类型分为 “输入事件(如onclick)” 和“语义事件(如onsubmit)”。

      通过javascript指定原始事件处理程序的方式,就是将一个函数值赋值给一个事件处理程序属性,每个元素(包括window 和 document)都有自己的事件处理程序属性,这些程序通常全部小写(如:onclick)。将这些属性的值设置成一个函数,即可指定事件处理程序。如下所示:

    1 var test1 = document.getElementById("test1");
    2 test1.onclick = function(){
    3     alert("绑定点击事件");
    4 }

      或者直接通过JS代码作为HTML性质值完成事件绑定:

    <a id="test1" href="javascript:void(0)" onclick="alert('点击事件绑定')">test1</a>

      通过给当前元素的属性指定事件处理程序,是在当前元素的作用域中运行,因此程序中的this引用的是当前元素,再看个例子:

    1  var test1 = document.getElementById("test1");
    2  test1.onclick = function(){
    3       alert(this.id); //"test1"
    4  }

      因为事件的处理程序是作为JS的属性,因此可以使用JS属性显式调用:

    document.getElementById("test1").onclick();//窗口弹出对话框:"点击事件绑定"

      因为事件的处理程序是作为JS的属性,因此也可以通过将属性值设置为null 的方式来达到删除事件程序的目的

    document.getElementById("test1").onclick = null ;  //删除点击事件

     *优点:所有浏览器都兼容

     *缺点:1)逻辑与显示没有分离;

        2)相同事件的监听函数只能绑定一个,后绑定的会覆盖掉前面的,如:a.onclick = func1; a.onclick = func2;将只会执行func2中的内容。

        3)无法通过事件的冒泡、委托等机制 完成更多事情。

        在当前web程序模块化开发以及更加复杂的逻辑状况下,这种方式显然已经落伍了,所以在真正项目中不推荐使用,平时写点博客小例子啥的倒是可以,速度比较快。

    IE事件模型

      在IE事件模型中,事件的表示作为全局函数window的一个属性event。在ie中(ie8及更早),默认的全局属性event值为null,然后在进入事件处理程序中时,系统将window.event 的值设置为当前事件类型,并且在每次事件处理完成之后 又将window.event 的值设置成null。为了求证,我们在IE8下运行如下代码:

    1 console.log(window.event); //null 
    2 window.onload = function(){
    3     console.log(window.event) // Event {isTrusted: true, type: "load", target: document, currentTarget: Window, eventPhase: 2…}
    4     setTimeout(function(){console.log(window.event);},100) //null
    5 }

      因此我们可以得知

        1.在html运行时,window.event 被定义,初始值为null。

        2.在我们进入window.onload 函数中时,window.event 的值为当前类型的事件。

        3.当我们完成事件过程后,window.event 又被设置为null。

      IE的事件模型执行过程只有两步,对应DOM2事件的 2(事件处理),3(事件冒泡) 阶段。先执行元素的监听函数,然后事件沿着父节点一直冒泡到document。

      IE模型下的事件绑定方式与DOM不同,绑定监听函数的方法是:attachEvent( "eventType","handler"),其中evetType为事件的类型,如onclick,注意要加’on’。解除事件监听器的方法是 detachEvent("eventType","handler" )

      与原始事件模型在当前元素上定义事件不同,使用attachEvent()方法的情况下,事件的处理程序会在全局作用域中运行,因此 this 等于 window。

    1 /*IE*/
    2 var test1 = document.getElementById("test1");
    3 test1.attachEvent("onclick",function(){
    4     alert(this === window); //true
    5 })
    6 //attachEvent 方法可以为一个元素添加多个事件处理程序,后添加的先执行
    7 test1.attachEvent("onclick",function(){
    8     alert("helloWord"); //先helloWord后true
    9 })

      *IE的事件模型已经可以解决原始模型的三个缺点,但其自己的缺点就是兼容性,只有IE系列浏览器才可以这样写。

    DOM2事件模型(标准)

      此模型是W3C指定的标准模型,因此我们现在使用的现代浏览器(IE6~IE8 除外),都遵循标准事件模型,在DOM2模型中事件有一个特殊的传播过程,分为三个阶段: 

        (1)capturing phase:事件捕获阶段。事件被从document一直向下传播到目标元素,在这过程中依次检查经过的节点是否注册了该事件的监听函数,若有则执行。

        (2)target phase:事件处理阶段。事件到达目标元素,执行目标元素的事件处理函数.

        (3)bubbling phase:事件冒泡阶段。事件从目标元素上升一直到达document,同样依次检查经过的节点是否注册了该事件的监听函数,有则执行。

      所有的事件都会经历事件捕获阶段(capturing phase),但是只有部分事件会经历事件冒泡(bubbling phase),例如:submit就不会被事件冒泡。

      “DOM2事件模型”,定义了两个方法用来指定和删除事件处理程序:

        addEventListener("eventType","handler","true|false");和 removeEventListner("eventType","handler","true!false");

        其中eventType指事件类型,注意不要加‘on’前缀,与IE下不同。第二个参数是处理函数,第三个即用来指定是否在捕获阶段进行处理,一般设为false来与IE保持一致。

    1 var test1 = document.getElementById("test1");
    2 test1.addEventListener("click",function(e){
    3     console.log(e);  //MouseEvent
    4     console.log(this); //当前监听的元素 test1对应的标签
    5 },false)

      *当我们了解了事件的三种模型之后,我们发现他们种会存在区别。为了更好的兼容各种浏览器版本,并且保证代码能在大多数浏览器下一致地运行,我们只需要关注冒泡阶段,并且整理一份跨浏览器的事件处理程序:

     1 var EventUtil = {
     2     /**
     3      * 添加事件
     4      * @param {Object} element
     5      * @param {Object} type
     6      * @param {Object} hander
     7      */
     8     addHander: function(element, type, hander) {
     9         if (element.addEventListener) {
    10             element.addEventListener(type, hander, false);
    11         } else if (element.attachEvent) {
    12             element.attachEvent("on" + type, hander);
    13         } else {
    14             element["on" + type] = hander;
    15         }
    16     },
    17     /**
    18      * 移除事件
    19      * @param {Object} element
    20      * @param {Object} type
    21      * @param {Object} hander
    22      */
    23     removeHander: function(element, type, hander) {
    24         if (element.removeEventListener) {
    25             element.removeEventListener(type, hander, false);
    26         } else if (element.detachEvent) {
    27             element.detachEvent("on" + type, hander);
    28         } else {
    29             element["on" + type] = null;
    30         }
    31     },
    32     /**
    33      * 取得当前事件对象
    34      * @param {Object} event
    35      */
    36     getEvent: function(event) {
    37         return event ? window.event : event;
    38     },
    39     /**
    40      * 取得触发事件的目标元素对象
    41      * @param {Object} event
    42      */
    43     getTarget: function(event) {
    44         return event.target || event.srcElement;
    45     },
    46     /**
    47      * 阻止默认的事件触发
    48      * @param {Object} event
    49      */
    50     preventDefault: function(event) {
    51         if (event.preventDefault) {
    52             event.preventDefault();
    53         } else {
    54             event.returnValue = false;
    55         }
    56     },
    57     /**
    58      * 阻止事件冒泡
    59      * @param {Object} event
    60      */
    61     stopPropagation: function(event) {
    62         if (event.stopPropagation) {
    63             event.stopPropagation();
    64         } else {
    65             event.cancelBubble = true;
    66         }
    67     }
    68 }

     e.target与e.currentTarget

      前文多次使用到事件(event)的target 属性,这里不得不提到与之相类似的 currentTarget属性,那么他们分别代表什么,并且有什么作用和区别?下面我们先看《javascript高级程序设计》中对着两个属性的说明:

     
    属性/方法 类型 读/写 说明
    target Element 只读 事件的目标
    currentTarget Element 只读 其事件处理程序当前正在处理事件的那个元素

      在事件处理程序内部,对象的this始终等于currentTarget的值,而target则只包含事件的实际目标。如果将事件处理程序指定给了目标元素,则 this、currentTarget、target 包含相同的值。如下例子:

    1 <div id="btnPanel" style="background: #ccc;padding: 40px;">
    2     <input type="button" name="btn" id="btn" value="确定" />
    3 </div>
    1 var btn = document.getElementById("btn");
    2 btn.onclick = function(event) {
    3     alert(event.currentTarget === this); //true
    4     alert(event.target === this); //true
    5 }

      由于 click 事件的目标是按钮,并且我们将点击事件的处理也是指定给了按钮。因此 currentTaget 、target 、 this 值相等。

      思考:如果我们将 click 事件绑定到 按钮父容器上,那么我们在点击按钮,由于事件冒泡 触发父容器 click事件时。 这三者有什么变化?修改上面js部分代码如下:

    1 var btnPanel = document.getElementById("btnPanel");
    2 btnPanel.onclick = function(event) {
    3     alert(event.currentTarget === btnPanel); //true
    4     alert(this === btnPanel);  //true
    5     alert(event.target === document.getElementById("btn")); //true
    6 }

      当我们点击这个例子中的按钮时,this和currentTarget 都等于 这个div,因为事件处理程序是注册到这个div上的 。 最终 target 等于按钮元素,是因为 我们在点击按钮时,由于按钮上没有注册 click 事件处理程序, 结构 click 事件就冒到了 父容器div 并触发了click。这个按钮 是 click 事件的真正目标。

      *总结:通过如上两个例子,我们可以这样理解:

       1.target 永远 等于 事件的真正目标 (如click事件中,target永远等于我们鼠标点击的最上层元素);

       2.currentTaget 始终等于this。等于 事件处理程序绑定的这个元素。(如: 谁.onclick = hander, “谁”就是currentTaget,也就是最终要冒泡到“谁”上触发事件的元素);

    取消事件冒泡(stopPropagation)和取消事件的默认行为(preventDefault)

      stopPropagation() 方法用于立即停止事件在DOM层次中的传播,即取消进一步的事件捕获和冒泡。 

     1 var btn = document.getElementById("btn");
     2 btn.onclick = function(event) {
     3     alert("click for btn"); 
     4     event.stopPropagation();
     5 }
     6 
     7 var btnPanel = document.getElementById("btnPanel");
     8 btnPanel.onclick = function(event) {
     9     alert("click for div"); 
    10 }

      和之前例子的不同是,我们在btn和div上都绑定了click事件,并且我们在btn的事件程序中调用event.stopPropagation()。当我们点击按钮时,页面只弹出一个提示框“click for btn”,是由于我们在进入btn的事件时,阻止了事件在dom中的传播,因此不会触发 div上绑定的点击事件。如果去掉event.stopPropagation(),则弹出两个提示对话框 先触发 btn事件,再经由事件冒泡 触发div事件。

      只有cancelable 属性为true的事件,才可以使用preventDefault() 来取消其默认行为。

    1 <form action="http://www.baidu.com" method="post">
    2     <a id="baiduA" href="http://www.baidu.com">百度</a>
    3     <input id="submitBtn" type="submit" value="提交"/>
    4 </form>

      我们都知道,a标签在点击的时候 会触发默认的跳转事件,在form中点击submit类型按钮,会自动post并且跳转到action指定的页面。我们现在要求,点击a标签或者submit按钮时,只提示 不做跳转 或者 提交。使用preventDefault();取消其默认事件的行为:

     1 var subBtn = document.getElementById("submitBtn");
     2 subBtn.onclick = function(e){
     3     alert("subBtn");
     4     e.preventDefault();
     5 }
     6 
     7 var baiduA = document.getElementById("baiduA");
     8 baiduA.onclick = function(e){
     9     alert("baiduA");
    10     e.preventDefault();
    11 }

       注:在IE事件对象中,也有取消默认事件,和阻止事件冒泡的属性。

        window.event.cancelBubble = true 对应 stoppropagation();

          window.event.returnValue  = false 对应 preventDefault();

    从模拟事件浅谈dispatchEvent

    事件经常有用户操作火通过其他浏览器的功能来触发,比如点击某些按钮,输入某些值等等,但是,在一些特定的情况下,我们需要我们的事件在任意时刻都能通过javascript来触发。并且某些事件还是我们自己定义的,这个时候我们可以使用IE下fireEvent方法,高级浏览器(chrome,firefox等)有dispatchEvent方法。

    例如在ie下看看这个例子:

     1 //document上绑定自定义事件ondataavailable
     2 document.attachEvent('ondataavailable', function(event) {
     3     alert(event.eventType);
     4 });
     5 var obj = document.getElementById("obj");
     6 //obj元素上绑定click事件
     7 obj.attachEvent('onclick', function(event) {
     8     alert(event.eventType);
     9 });
    10 //调用document对象的createEventObject方法得到一个event的对象实例。
    11 var event = document.createEventObject();
    12 event.eventType = 'message';
    13 //触发document上绑定的自定义事件ondataavailable
    14 document.fireEvent('ondataavailable', event);
    15 //触发obj元素上绑定click事件
    16 document.getElementById("test").onclick = function() {
    17     obj.fireEvent('onclick', event);
    18 };

    再看看高级浏览器(chrome,firefox等)的例子:

     1 //document上绑定自定义事件ondataavailable
     2 document.addEventListener('ondataavailable', function(event) {
     3     alert(event.eventType);
     4 }, false);
     5 var obj = document.getElementById("obj");
     6 //obj元素上绑定click事件
     7 obj.addEventListener('click', function(event) {
     8     alert(event.eventType);
     9 }, false);
    10 //调用document对象的 createEvent 方法得到一个event的对象实例。
    11 var event = document.createEvent('HTMLEvents');
    12 // initEvent接受3个参数:
    13 // 事件类型,是否冒泡,是否阻止浏览器的默认行为
    14 event.initEvent("ondataavailable", true, true);
    15 event.eventType = 'message';
    16 //触发document上绑定的自定义事件ondataavailable
    17 document.dispatchEvent(event);
    18 var event1 = document.createEvent('HTMLEvents');
    19 event1.initEvent("click", true, true);
    20 event1.eventType = 'message';
    21 //触发obj元素上绑定click事件
    22 document.getElementById("test").onclick = function() {
    23     obj.dispatchEvent(event1);
    24 };

    在以上两个例子中,我们都会发现,在事件触发的时候,我们需要对触发的方法传一个事件对象进去,并且这个事件对象使我们通过js自己创建的。

    在ie 中为:document.createEventObject();

    在常规浏览器中:document.createEvent('HTMLEvents');

    createEvent()方法返回新创建的Event对象,支持一个参数,表示事件类型,具体见下表:

    参数事件接口初始化方法
    HTMLEvents HTMLEvent initEvent()
    MouseEvents MouseEvent initMouseEvent()
    UIEvents UIEvent initUIEvent()

     对于标准浏览器,其提供了可供元素触发的方法:element.dispatchEvent(). 不过,在使用该方法之前,我们还需要做其他两件事,及创建和初始化。因此,总结说来就是:

    1 document.createEvent()
    2 event.initEvent()
    3 element.dispatchEvent()

     *总结,对于事件的触发,dispatchEvent 或者 fireEvent 也只是了解了对事件触发的调用方式,本篇不作过多的介绍。应该会在之后单独起一篇,事件模拟,自定义事件的文章。

     事件代理

    javascript事件是所有网页互动性的根本,因此在我们编写前端交互的时候,事件绑定作为再普通不过的交互操作了。在传统的事件处理中,你根据需求给每一个元素添加或者 删除事件处理器,然而事件处理器可以导致内存泄露或者性能下降(你用的越多风险越大)。例如以下例子

    一:当我们点击每个li时,控制台打出 当前点击元素的 innerHtml 

    1 <ul id="ul">
    2   <li>aaaaaaaa</li>
    3   <li>bbbbbbbb</li>
    4   <li>cccccccc</li>
    5 </ul>

     传统的做法:为每个li绑定点击事件:

    1 window.onload = function(){
    2     var ul = document.getElementById("ul");
    3     var lis = ul.getElementsByTagName("li");
    4     for(var i = 0 ; i < lis.length; i ++){
    5         lis[i].onclick = function(){
    6             console.log(this.innerHTML);
    7         }
    8     }
    9 }

    这样我们就可以做到li上面添加鼠标事件,但是如果说我们可能有很多个li用for循环的话就比较影响性能。

    实际开发情况中,我们的li大多数是根据数据集合,动态组装生成的。此时就需要我们每生成一个li 就给这个li 绑定一个点击事件。效率低,容易出错

    思考:1.有没有一种可行的方法让我每次只需完成一次事件绑定的操作?

       2.或者我在li的父容器上,加入某种事件,让我在每次点击li时,由父容器的事件判断我当前点击的哪个li元素,并执行特定的操作?

    答:事件代理。

    下面我们可以用事件代理的方式来实现这样的效果。html不变

     1 window.onload = function(){
     2     var ul = document.getElementById("ul");
     3     var lis = ul.getElementsByTagName("li");
     4     
     5     ul.onclick = function(e){
     6         /*
     7         这里要用到事件源:event 对象,事件源,不管在哪个事件中,只要你操作的那个元素就是事件源。
     8         ie:window.event.srcElement
     9         标准下:event.target
    10         nodeName:找到元素的标签名
    11         */
    12         var e  = e || window.event;
    13         var target = e.target || e.srcElement;
    14         if(target.nodeName.toUpperCase() == "LI"){
    15             alert(target.innerHTML);
    16         }
    17     }
    18 }

    这样,我们就使用事件代理完成在父容器绑定点击事件,当我们点击子元素li时,根据事件源得到当前点击的target元素。这样就算li是动态生成的,在点击的时候也会去获取到新的节点li。并执行对应操作。

    原理:事件冒泡以及目标元素。当一个元素的事件被触发时,同样的事件将会在这个元素的所有祖先元素中被触发,这一过程称之为事件冒泡。这个事件从原始元素一直冒泡到dom树最上层。并且所有的事件触发的目标元素都是最开始的元素(target || srcElement)因此我们可以根据这一原理来达到触发原始元素事件的目的。

    这样做的好处:

      1.将所有同类元素的事件操作代理给其父元素,减少了事件绑定过程,避免了100个li,1000个li 或者更多li的循环绑定事件,有效减少内存占用,避免元素过多导致浏览器内存泄露,提高效率。

      2.在DOM更新后无须重新绑定事件处理器了。

    接下来我们利用事件代理封装一个通用的方法:

     1 <!DOCTYPE html>
     2 <html>
     3 
     4     <head lang="en">
     5         <meta charset="UTF-8">
     6         <title></title>
     7         <style type="text/css" rel="stylesheet">
     8             p,
     9             h3 {
    10                 width: 200px;
    11                 height: 100px;
    12                 border: 1px solid pink;
    13                 margin: 20px auto;
    14             }
    15         </style>
    16     </head>
    17 
    18     <body>
    19         <div id="pElement">
    20             <p>p的点击事件</p>
    21             <h3>h3的双击事件</h3>
    22         </div>
    23     </body>
    24 </html>
     1 var pElement = document.getElementById('pElement');
     2 //事件代理整理
     3 EventTarget.prototype.on = function(eventType, selector, callback) {
     4     var _event = function(e) {
     5         var target = event.target || event.srcElement;
     6         if (target === selector) {
     7             callback(e);
     8         }
     9     }
    10     if (this.addEventListener) {
    11         this.addEventListener(eventType, _event, false);
    12     } else if (this.attachEvent) {
    13         this.attachEvent("on" + eventType, _event);
    14     } else {
    15         this["on" + eventType] = _event;
    16     }
    17 }
    18 
    19 //可扩展的选择器 添加id class 属性等筛选
    20 function _getTarget(targetName) {
    21     return document.getElementsByTagName(targetName)[0];
    22 }
    23 
    24 //给p元素绑定点击事件
    25 pElement.on('click', _getTarget("p"), function(e) {
    26     alert(e.target.innerText)
    27 });
    28 
    29 //给h3 绑定双击事件
    30 pElement.on('dblclick', _getTarget("h3"), function(e) {
    31     alert(e.target.innerText)
    32 });

     

  • 相关阅读:
    不平衡数据集的处理方法
    爬楼梯问题
    Tensorflow object detection API 搭建物体识别模型(四)
    基于jieba,TfidfVectorizer,LogisticRegression进行搜狐新闻文本分类
    利用jieba,word2vec,LR进行搜狐新闻文本分类
    Arthas协助排查线上skywalking不可用问题
    springboot集成jpa操作mybatis数据库
    es性能调优---写优化操作
    Skywalking的存储配置与调优
    docer
  • 原文地址:https://www.cnblogs.com/pandaBrother/p/5627408.html
Copyright © 2011-2022 走看看