zoukankan      html  css  js  c++  java
  • 初步理解JS的事件机制

    一、事件流(捕获,冒泡)
     
    事件流:指从页面中接收事件的顺序,有冒泡流和捕获流。
    当页面中发生某种事件(比如鼠标点击,鼠标滑过等)时,毫无疑问子元素和父元素都会接收到该事件,可具体顺序是怎样的呢?冒泡和捕获则描述了两种不同的顺序。
     
    DOM2级事件规定事件流包括三个阶段,如图:
     
     
    假如我们点击一个div, 实际上是先点击document,然后点击事件传递到div,而且并不会在这个div就停下,div有子元素就还会向下传递,最后又会冒泡传递回document,如上图
     
    为了兼容更多的浏览器,非特殊情况一般我们都是把事件添加到在事件冒泡阶段。
     
    二、事件处理程序
     
    DOM0级事件处理程序
     
    例子:
     
    1 var btn5 = document.getElementById('btn5');
    2 btn5.onclick=function(){
    3    console.log(this.id);//btn5   
    4 };
     
    注意:基于DOM0的事件,对于同一个dom节点而言,只能注册一个,后边注册的 同种事件 会覆盖之前注册的。利用这个原理我们可以解除事件,btn5.onclick=null;其中this就是绑定事件的那个元素;
     
    以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理;
     
    DOM2级事件处理程序
     
    DOM2支持同一dom元素注册多个同种事件,事件发生的顺序按照添加的顺序依次触发(IE是相反的)。
    DOM2事件通过addEventListener和removeEventListener管理
     
    //addEventListener(eventName,handlers,boolean);removeEventListener(),两个方法都一样接收三个参数,第一个是要处理的事件名,第二个是事件处理程序,第三个值为false时表示在事件冒泡阶段调用事件处理程序,一般建议在冒泡阶段使用,特殊情况才在捕获阶段;
    注意:通过addEventListener()添加的事件处理程序只能用removeEventListener()来移除,并且移除时传入的参数必须与添加时传入的参数一样;比如
     
    例子:
     
    1 var btn2 = document.getElementById('btn2');
    2 var handlers = function () {
    3    console.log(this.id);
    4 };
    5 
    6 btn2.addEventListener('click',handlers,false);
    7 
    8 btn2.removeEventListener('click',handlers.false);
     
    IE事件处理程序
     
    //IE事件处理程序(IE和Opera支持)
    //IE用了attachEvent(),detachEvent(),接收两个参数,事件名称和事件处理程序,通过attachEvent()添加的事件处理程序都会被添加到冒泡阶段,所以平时为了兼容更多的浏览器最好将事件添加到事件冒泡阶段,IE8及以前只支持事件冒泡;
     
    例子:
     
    1 var btn3 = document.getElementById('btn3');
    2 var handlers2=function(){
    3    console.log(this===window);//true,注意attachEvent()添加的事件处理程序运行在全局作用域中;
    4 };
    5 btn3.attachEvent('onclick',handlers2);
     
    跨浏览器事件处理程序
     
     1 //创建的方法是addHandlers(),removeHandlers(),这两个方法属于一个叫EventUtil的对象;但是这个没有考虑到IE中作用域的问题,不过就添加和移除事件还是足够的。
     2  
     3 var EventUtil = {
     4    addHandlers: function (element, type, handlers) {
     5       if (element.addEventListener) {
     6          element.addEventListener(type, handlers, false);
     7       } else if (element.attachEvent) {
     8          element.attachEvent(on + type, handlers);
     9       } else {
    10          element['on' + type] = handlers;
    11       }
    12    },
    13    removeHandlers: function (element, type, handlers) {
    14       if (element.removeEventListener) {
    15          element.removeEventListener(type, handlers, false);
    16       } else if (element.detachEvent) {
    17          element.detachEvent(on + type, handlers);
    18       } else {
    19          element['on' + type] = null;
    20       }
    21    }
    22 };
     
    例子:
     
    1 var btn4=document.getElementById('btn4');
    2 var handlers3=function(){
    3    console.log('123')
    4 };
    5 EventUtil.addHandlers(btn4,'click',handlers3);
    6 //……
    7 EventUtil.removeHandlers(btn4,'click',handlers3); 
     
    在同一个对象上注册事件,并不一定按照注册顺序执行,冒泡或捕获模式会影响其被触发的顺序;
     
    三、事件对象
     
    兼容触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含了所有与事件有关的信息,比如导致事件的元素target,事件的类型,及其他特定的相关信息。例如鼠标操作导致的事件对象中会包含鼠标的位置,单双击等,而键盘操作导致的事件对象会包含按下的键等信息;
     
    事件被触发时,会默认给事件处理程序传入一个参数e , 表示事件对象;通过e,我们可以获得其中包含的与事件有关的信息;
     
    只有在事件处理程序执行期间,event对象才会存在,一旦事件处理程序执行完毕,event对象就会被销毁;
     
    DOM中的事件对象
     
    兼容DOM的浏览器会自动将一个事件对象event传递给事件处理程序
     
    ps:关于事件对象中的this,target,currentTarget,看个例子:(注:event.target不支持IE浏览器,应该用event.srcElement;还有 IE中通过attachment添加的事件是运行在全局作用域中的,this===window)
     
    当事件绑定在真正的目标元素上时,this===target===currentTarget ,而且绑定事件时是否捕获结果都是一样的,此时eventParse==2;
     
     1 //this,target,currentTarget,this===currentTarget
     2 $('#outer').on('click','#center',function(e){
     3    console.log(this.id);//on()中间的参数是个过滤器,相当于将事件绑定到了#center上;此时点击#center将不会触发事件
     4    console.log(e.target.id);
     5    console.log(e.currentTarget.id);
     6 });
     7 
     8 $('#outer').on('click',function(e){
     9    console.log(this.id);
    10    console.log(e.target.id);
    11    console.log(e.currentTarget.id);
    12 });
    13  
    14 event.stopPropagation()不能简单说阻止了事件的冒泡,其实也阻止了事件的继续捕获,确切的说应该是阻止事件的进一步传播
    15  
    16 var d1 = document.getElementById('d1');
    17 d1.addEventListener('click', function (evt) {
    18     console.log('d1');
    19     evt.stopPropagation();
    20 }, true);
    21 var d2 = document.getElementById('d2');
    22 d2.addEventListener('click', function () {
    23     console.log('d2');
    24 }, true);
     
    输出结果是:s1;
     
    event.stopPropagation()可以组织事件的传播,但它阻止不了绑定在该元素上的其他函数的执行,比如将上面例子改一下,给d1再绑定一个事件,同时d1的第一个事件中改成event.stopImmediatePropagation(),那么第二个事件也会被阻止,它不仅阻止事件的传播还阻止后续事件的执行;
     
     1 var d1 = document.getElementById('d1');
     2 d1.addEventListener('click', function (evt) {
     3     console.log('d1');
     4     evt.stopImmediatePropagation();
     5 }, true);
     6 d1.addEventListener('click', function (evt) {
     7     console.log('d1+1');
     8 }, true);
     9 var d2 = document.getElementById('d2');
    10 d2.addEventListener('click', function () {
    11     console.log('d2');
    12 }, true);
     
    IE中的事件对象
     
    IE中event参数是未定的,事件对象是作为window的一个属性存在的,因此可以通过window.event来访问event对象,不同于DOM级中event是作为参数直接传入和返回;
     
    事件函数是以on开头的;
     
    属性上也有一些不同,如下:
     
     
    跨浏览器的事件对象
     
    虽然DOM和IE中对象不同,但是两者event中的全部信息和方法都是类似的只是实现方式不同,可以用前面提到过的EventUtil对象来求同存异
     
     1 var EventUtil = {
     2     addHandler: function (element, type, handler) {
     3         if (element.addEventListener) {
     4             element.addEventListener(type, handler, false);
     5         } else if (element.attachEvent) {
     6             element.attachEvent(on + type, handler);
     7         } else {
     8             element['on' + type] = handler;
     9         }
    10     },
    11 
    12     getEvent: function (event) {
    13         return event ? event : window.event;
    14 
    15     },
    16 
    17     getTarget: function (event) {
    18         return event.target || event.srcElement;
    19     },
    20 
    21     preventDefault: function (event) {
    22         if (event.preventDefault) {
    23             event.preventDefault();
    24         } else {
    25             event.returnValue = false;
    26         }
    27     },
    28 
    29     stopPropagation: function (event) {
    30         if (event.stopPropagation) {
    31             event.stopPropagation();
    32         } else {
    33             event.cancelBubble = true;
    34         }
    35     },
    36 
    37     removeHandler: function (element, type, handler) {
    38         if (element.removeEventListener) {
    39             element.removeEventListener(type, handler, false);
    40         } else if (element.detachEvent) {
    41             element.detachEvent(on + type, handler);
    42         } else {
    43             element['on' + type] = null
    44         }
    45     }
    46    
    47 };
     
    使用上面的EventUtil对象,举个例子:
     
    1 var myBtn=document.getElementById('my-btn');
    2 btn.onclick=function(event){
    3     event=EventUtil.getEvent(event);
    4     EventUtil.preventDefault(event);
    5 };
     
    个例子:必须要假设有一个事件对象event传入了事件处理程序中,要使用EventUtil中的方法需要将该事件对象传给那些方法,该事件对象则需要通过其getEvent方法来获得;
     
    四、事件委托
     
    每当将事件处理程序制定给元素时,运行中的浏览器代码与支持页面交互的JS代码之间就会建立一个连接,而这种连接越多,页面执行起来就越慢。考虑内存和性能问题,为了解决事件处理程序过多的问题,采用事件委托变得很有必要。(考虑到内存,也应该尽量减少不必要的事件处理程序,对于内存中过时不用的’空事件处理程序’,也是很有必要将其移除的;)
     
    因为冒泡机制,比如既然点击子元素,也会触发父元素的点击事件,那我们完全可以将子元素的事件要做的事写到父元素的事件里,也就是将子元素的事件处理程序写到父元素的事件处理程序中,这就是事件委托;利用事件委托,只指定一个事件处理程序,就可以管理某一个类型的所有事件;例如:
     
     1 var myLinks=document.getElementById('my-links');
     2 myHandlers=function(event){
     3     event=EventUtil.getEvent(event);
     4     var target=EventUtil.getTarget(event);
     5 
     6     switch(target.id){
     7         case 'item-1':
     8             location.href='http://www.cnblogs.com/lazychen/';
     9             break;
    10         case 'item-2':
    11             document.title='event';
    12             break;
    13         case 'item-3':
    14             console.log('hi');
    15             break;
    16     }
    17 };
    18 EventUtil.addHandler(myLinks,'click',myHandlers);
     
    点击任何一个 li ,该点击事件依然会冒泡到父元素 ul 上,所以直接将点击 li 后要做的事写到了父元素的点击事件里;
     
    《JS高设 3rd》中列出了几种最适合采用事件委托的事件:click,mousedown,mouseup,keydown,keyup,keypress
     
    总结一下js委托相关的:
    • 因为把事件绑定到了父节点上,因此省了绑定事件。就算后面新增的子节点也有了相关事件,删除部分子节点不用去销毁对应节点上绑定的事件
    • 父节点是通过event.target来找对应的子节点的。(事件处理程序中的this值始终等于currentTarget的值,指向的是绑定到的那个元素)

    ps:文中盗了两张图~大大莫怪莫怪

      
     
  • 相关阅读:
    B/S架构
    RPC远程过程调用详解
    Ubuntu18.04安装MongoDB
    Python2.X SQLAlchemy @@tx_isolation警告
    Excel单元格内自动换行自动行高,打印预览出现内容缺失解决方案
    Winform应用的多语言设置
    单例模式创建窗口
    相似命名的字符串高效拼接
    利用dynamic动态创建对象
    设置全局快捷键
  • 原文地址:https://www.cnblogs.com/lazychen/p/5664788.html
Copyright © 2011-2022 走看看