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)
          })
        }
      }
<!doctype html> <html lang="zh-ch" id="html"> <head> <meta charset="utf-8" /> <meta content="IE=8" http-equiv="X-UA-Compatible"/> <title>事件代理 by 司徒正美</title> <script type="text/javascript"> window.onload = function(){ 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) }) } } </script> </head> <body id="body"> <h1>事件代理</h1> <ul> <li>无序列表1</li> <li>无序列表2</li> <li>无序列表3</li> <li>无序列表4</li> </ul> </body> </html>

运行代码

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

         var ul = document.getElementsByTagName("ul")[0]
        addEvent(ul,"click",function(e){
          var el = e.srcElement || e.target;
          if(el.tagName === "LI")
            alert(el.innerHTML)
        })
<!doctype html> <html lang="zh-ch" id="html"> <head> <meta charset="utf-8" /> <meta content="IE=8" http-equiv="X-UA-Compatible"/> <title>事件代理 by 司徒正美</title> <script type="text/javascript"> window.onload = function(){ 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 ul = document.getElementsByTagName("ul")[0] addEvent(ul,"click",function(e){ var el = e.srcElement || e.target; if(el.tagName === "LI") alert(el.innerHTML) }) } </script> </head> <body id="body"> <h1>事件代理</h1> <ul> <li>无序列表1</li> <li>无序列表2</li> <li>无序列表3</li> <li>无序列表4</li> </ul> </body> </html>

运行代码

但是这样复用性不好,我们需要一个真正的事件代理函数。如果用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)
        });
<!doctype html> <html lang="zh-ch" id="html"> <head> <meta charset="utf-8" /> <meta content="IE=8" http-equiv="X-UA-Compatible"/> <title>事件代理 by 司徒正美</title> <script type="text/javascript"> window.onload = function(){ 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 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) }); } var aaa = function(){ var li = document.createElement("li"); li.innerHTML = "动态添加"+(aaa.aa++) document.getElementsByTagName("ul")[0].appendChild(li) } aaa.aa = 0 </script> </head> <body id="body"> <ul> <li>无序列表1</li> <li>无序列表2</li> <li>无序列表3</li> <li>无序列表4</li> </ul> <button type="button" onclick="aaa()">动态添加</button> </body> </html>

运行代码

这里有个奇妙的现象,当我们执行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);
        }
<!doctype html> <html lang="zh-ch" id="html"> <head> <meta charset="utf-8" /> <meta content="IE=8" http-equiv="X-UA-Compatible"/> <title>事件代理 by 司徒正美</title> <script type="text/javascript"> window.onload = function(){ 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 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); } delegate("li","click",function(e){ alert(this.innerHTML+e.type) }); } var aaa = function(){ var li = document.createElement("li"); li.innerHTML = "动态添加"+(aaa.aa++) document.getElementsByTagName("ul")[0].appendChild(li) } aaa.aa = 0 </script> </head> <body id="body"> <ul> <li>无序列表1</li> <li>无序列表2</li> <li>无序列表3</li> <li>无序列表4</li> </ul> <button type="button" onclick="aaa()">动态添加</button> </body> </html>

运行代码

为了尽量接近现实中的复杂需求,我最后还是给出一个小型选择器吧,注意只能运行于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)
        });
<!doctype html> <html lang="zh-ch" id="html"> <head> <meta charset="utf-8" /> <meta content="IE=8" http-equiv="X-UA-Compatible"/> <title>事件代理 by 司徒正美</title> <script type="text/javascript"> window.onload = function(){ 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 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) }); } 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 aaa = function(){ var li = document.createElement("li"); li.innerHTML = "动态添加"+aaa.aa++ $("#list")[0].appendChild(li) } aaa.aa = 0; </script> </head> <body id="body"> <h1>司徒正美:事件代理</h1> <ul id="list"> <li>无序列表1</li> <li>无序列表2</li> <li>无序列表3</li> <li>无序列表4</li> </ul> <p>下面的无序列表是没有id的!</p> <ul> <li>无序列表1</li> <li>无序列表2</li> <li>无序列表3</li> <li>无序列表4</li> </ul> <button type="button" onclick="aaa()">动态添加</button> </body> </html>

运行代码

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值