• javascript事件系统的发展史2


    综观一个系统的发展,无非是发现一个问题就把它独立出来解决掉,因此它的所有模块(或者分支)其实针对独立的问题,这样我们对这些问题若有什么更好的解决方法,替换相应的模块便是,要不代码混杂在一起没法看了。上一部分就提到了,attachEvent的事件列队出了些问题,我们要手动构建列队了。所谓列队就是有先有后的问题,因此DE大神搞了一个全局的uuid,分配给每个回调函数。但要注意,每个回调所针对的事件源与事件类型,因此这不可能一个数组搞定。看下图:

    el.attachEvent("onmouseenter",function(){alert(window.event.type)})
     |       |             |             |                |        
     |       |             |             |                |
    事件源 监听器        事件类型       回调函数          事件对象
    

    事件源,其实el也一定是事件源,如某同类型事件从文档树下冒泡上来,或者顶层对象要捕获它下面的某个同类型事件,那它就成了currentTarget。为简单起见,我称它为事件源。这个事件源可以绑定很多监听器,每个监听器可以针对不同的事件类型,当然这也意味着有许多回调函数。当我们发现attachEvent多个同类型事件时,回调函数的次序出错时,意味着,如果我们还要它时,每种事件类型只能绑定监听器一次。那不就意味着,一个监听器只有一个回调函数,因为只有一个次序肯定不会出错!但怎么添加更多回调函数呢?嗯,我们需要一个数组。数组里面的回调函数才是我们自己添加的,然后一个for循环执行它们就是!

          var queue = [];   
          el.attachEvent("onmouseenter",function(){
            var e = window.event;
            for(var i=0,n=queue.length;i<n;i++){
              queue[i].call(this,e)//queue[i]为我们自己的回调函数,this指向el
            }
          });
          queue.push(callback)
    

    这样就实现监听事件与添加回调函数相分离。但是一个事件类型就要搞一个queue,而且这些事件类型即使是同一类型还有事件源之分。那会产出巨量的全局变量。因此我们必须找个地方放置它们。DE大神在要监听的每个元素节点(或者文档对象什么的)上设置一个events属性,那是一个对象,然后以type-object的形式保存,type即为事件类型,object为一个对象,键为他的那个uuid,值为回调函数。下面是他的addEvent函数的一部分,完整代码这里(要FQ)

    function addEvent(element, type, handler) {
        if (!handler.$$guid) handler.$$guid = addEvent.guid++;
        if (!element.events) element.events = {};
        var handlers = element.events[type];
        if (!handlers) {
          handlers = element.events[type] = {};
          if (element["on" + type]) {
            handlers[0] = element["on" + type];
          }
        }
        handlers[handler.$$guid] = handler;
        element["on" + type] = handleEvent;
      };
    

    下面是它的events的结构:

            element.events = {
              "click":{
                1:fn1,
                3:fn3,
                4:fn4
              },
              "mousemove":{
                2:fn2,
                5:fn5
              }
            }
    

    这样储存结构注定了只能用最慢的for...in循环来遍历。嘛,限于当时人们的眼界,大神已经做得很好了。最佳的结构应该能对应我上面给出的示例,应该是这个样子:

            element.events = {
              "click":[fn1,fn3,fn4],
              "mousemove":[fn2,fn5]
            }
    

    纵观DE的事件大神的事件系统,可是当时是复杂的,是具模块化的,每个针对不同的问题进行处理:

    addEvent //为事件源某一个事件类型添加一个监听器(它的回调函数总是主处理器),并把回调函数放置到事件源的events属性上
    removeEvent //为事件源某一个事件类型逐一删减回调函数,
    handleEvent//主处理器,用于在里面遍历我们自己添加的回调函数
    fixEvent//也是在主处理器中执行,并只针对IE的事件源对象,为它添加标准浏览器的两个方法
    

    IE7发布后,引入新的内存泄漏。在IE7中,DOM对象不会被CG程序回收,只有离开页面时会被回收,但如果这时还被东西引用着就完蛋了,所以我们要清空元素节点上面的东西。如果我们把这个毛病解决了就很完美了,无奈DE大神不干了,这个问题留给其他继续发展的框架搞定。其中之一就是jQuery,它的事件系统就是基本DE大神的。jQuery面临的任务有如下几个:

    • 支持更多的事件类型,如FF下的滚轮事件,DOMMouseScroll是不能通过onXXX添加到事件源上的。
    • 分离事件源那个events属性,交由新式的缓存系统集中管理。
    • 让IE与标准浏览器的事件源对象更加标准化。

    至于事件代理,那是更后的事了。它最初都是由插件引入的,然后逐步发展到今日的规模。至于上面三点,我在《javascript 跨浏览器的事件系统》系列给出的相应的思路了:用缓存系统把element.events从元素上分离出来,取而代之是一个轻量的uuid,其对应的缓存体保存事件类型与回调函数的映射;创建一个伪事件对象将原生事件对象包裹起来,在它的原型添加w3c的事件方法;在fix函数中修正其事件属性,如左中右键,坐标,事件源等乱七八糟的东西,这样就几乎以假乱真了;在handle函数,我们监视每次回调函数执行的结果,如果为undefined就让它冒泡,如果为false就禁止冒泡与默认行为,如果isImmediatePropagationStopped的执行结果为true就断开循环,换言之,禁止同类型的回调函数的继续执行。这个东西是从Flash学回来的,称之为stopImmediatePropagation。当然,对于DOMNodeInserted、 DOMNodeRemoved、 DOMNodeRemovedFromDocument、 DOMNodeInsertedIntoDocument、 DOMAttrModifiedonclick这样高级的事件,onXXX是无能为力了,因此必须请回attachEvent与addEventListener。DOMMouseScroll。

          jQuery.event = {
            add:function(){},//为事件源某一个事件类型添加一个监听器(它的回调函数总是主处理器),并把回调函数放到缓存体中
            remove:function(){},//把回调函数从缓存体中删除,如果某一类型为空,则移除相应的监听器(DE大神的不会)
            fix:function(){},//将事件对象用伪事件对象包装起来,因为事件对象的属性都是只读的,这样才能添加与修正更多标准的属性
            handle:function(){},//根据事件类型从缓存体中取出,遍历执行,并根据回调函数的结果执行阻止冒泡与默认行为等方法
            trigger:function(){}//实现跨平台的充许传参的事件分派
          }
          jQuery.Event = function(){}//为事件对象添加w3c的那几个标准方法与少量属性
    

    如果类库看得多的话,就会知道全世界都在设法模拟mouseenter/leave,这是IE特有的事件,它们的优越性可见这篇文章《Goodbye mouseover, hello mouseenter》。由于标准浏览器不支持,我们必须用其他类似事件模拟它们。不用说,最近它们的是mouseover,mouseout。对于这样特殊的事件,还有domReady,jQuery引入了special系统,一个事件的子系统,绕了大圈让原生事件回到模拟事件的事件列队中。当然这样做有一个弊端,mouseente/leave在标准浏览器的事件类型总是错误的……看来,这special系统还不成熟呢!

    jQuery还引进入命名空间与事件分派,这对以后事件代理非常有用。那么第三部分继续!

      //利用同一个命名空间绑定三个事件
      //$('a').bind('keydown.key keypress.key keyup.key', function () { alert("nasamidesu"); });
      //然后卸载时就轻松了
      //$('a').unbind('.key');
    
  • 相关阅读:
    洛谷 1736 创意吃鱼法
    有多重限制的背包
    洛谷 1417 烹调方案
    2008 noip 传纸条
    环形石子合并 洛谷 1880 && hdu 3506 Monkey Party
    洛谷 1282 多米诺骨牌
    (金明的预算方案)依赖性的背包
    分组背包问题
    混合背包问题
    多重背包问题
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/1725353.html
Copyright © 2020-2023  润新知