Ruby's Louvre

每天学习一点点算法

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

javascript 跨浏览器的事件系统3

这部分说一下最近非常流行的事件代理。事件代理的实现简单来说,是把事件绑定到目标元素的祖先元素上,然后通过冒泡或捕获得到事件源,然后再判定事件源是否等于目标元素再执行回调函数。由于对目标元素的判定有时非常模糊,因此通过判定即可调用回调函数,这样,我们就达到一个监听器为许多事件源服务的目的。对于性能一向非常不怎么样的IE6来说,实在帮了一个大忙。

假如,有一个无序列表,点击弹出它的innerHTML,如果按事件绑定的做法,代码大概是这样:

var addEvent = function(el,type,callback,data){
    if ( el.addEventListener ) {//如自定义对象就绑定回调函数了
      el.addEventListener( type, callback, !!data );
    } else if ( el.attachEvent ) {
      el.attachEvent( "on" + type, function(){
        return callback.call(el,window.event)
      } );
    }
  }
  var els = document.getElementsByTagName("li")
  for(var i=0,n=els.length;i<n;i++){
    addEvent(els[i],"click",function(){
      alert(this.innerHTML)
    })
  }
}

如果用事件代理的做法大概是这样:

var ul = document.getElementsByTagName("ul")[0]
addEvent(ul,"click",function(e){
  var el = e.srcElement || e.target;
  if(el.tagName === "LI")
    alert(el.innerHTML)
})

但是这样复用性不好,我们需要一个真正的事件代理函数。如果用YUI与jQuery的人应该知道,这其中必然涉及到选择器,利用选择器进行匹配,实现上面的el.tagName === "LI",只不过适用性更广罢了。但现在我们不可能搞鼓个选择器出来,那可不是一下子就能实现的东西,况且它与我们的主题,为简单起见,我们就使用原生的标签选择器。换言之,新函数的参数之一,就是目标元素的tagName。其次,我们需要一个或一些代理元素,就像上面例子那个ul元素。我在《事件冒泡》一文中也给出各元素冒泡的情况了,若无视不能冒泡的事件,那么元素大抵是分上浮到from元素与上浮到文档这两种情况,当然还有上浮到window的情况,但最高只能上浮document。现在我们无视这些情况,强制为document。

var delegate = function(a,type,callback){
   var els = document.getElementsByTagName(a);
   addEvent(document,type,function(e){
     var el = e.srcElement || e.target;
     for(var i=0,n=els.length;i<n;i++){
       if(el === els[i]){
         return callback.call(el,e)
       }
     }
   },true);
 }
 delegate("li","click",function(e){
   alert(this.innerHTML+e.type)
 });

这里有个奇妙的现象,当我们执行delegate函数,LI元素的个数为4,每当我们动态添加一个时,它也会相应添加一个。轻松实现jQuery所谓的live函数。不过,当我们把els转换为纯数组时,它就失去这种动态性了。jQuery的选择器等价于getElementsByTagName吗?不等于!因此,我们要重新来过了!

var delegate = function(a,type,callback){
  var els = document.getElementsByTagName(a),
  tmp = [];
  for(var j=0,jn=els.length;j<jn;j++){
    tmp.push(els[j])
  }
  els = tmp;//强制转换为纯数组
  addEvent(document,type,function(e){
    var el = e.srcElement || e.target;
    for(var i=0,n=els.length;i<n;i++){
      if(el === els[i]){
        return callback.call(el,e)
      }
    }
  },true);
}

为了尽量接近现实中的复杂需求,我最后还是给出一个小型选择器吧,注意只能运行于IE8(要求是标准模式)与较新的标准浏览器下。为了实现jQuery.live的效果,我们对代码做了一些调整:

var $ = function(selector,context){
  context = context || document
  try{
    var els = context.querySelectorAll(selector),
    result = [],ri=0,i=0,n=els.length;
    for(;i<n;i++){
      result[ri++] = els[i]
    }
    return result;
  }catch(e){
    alert("你的浏览器不支持querySelectorAll")
  }
}
  var delegate = function(a,type,callback){
    addEvent(document,type,function(e){
      var els = $(a);
      var node = e.srcElement || e.target;
      for(var i=0,el;el = els[i++];){
        if(node === el){
          return callback.call(node,e)
        }
      }
    },true);
  }
  delegate("#list li","click",function(e){
    alert(this.innerHTML+e.type)
  });

就这样,一个粗糙的事件代理系统完成了。但有个问题,我们每次进入这个函数,就调用选择器,是不是很耗性能呢?因为动态添加的情况并不是每次都发生,我们只要比较原来的节点集合就行了,当发现原来的找不到,再调用选择器也不晚。第四部分我们将解决这问题,see you!

如果您觉得此文有帮助,可以打赏点钱给我支付宝1669866773@qq.com ,或扫描二维码

posted on   司徒正美  阅读(2066)  评论(4编辑  收藏  举报

编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
点击右上角即可分享
微信分享提示