js事件委托

Author Avatar
cowboy 9月 15, 2017
  • 在其它设备中阅读本文章

简述

事件委托也叫做事件代理,高程书上的解释为

事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

借用一个例子来说明:
有三个同事预计会在周一收到快递。为签收快递,有两种办法:一是三个人在公司门口等快递;二是委托给前台MM代为签收。现实当中,我们大都采用委托的方案(公司也不会容忍那么多员工站在门口就为了等快递)。前台MM收到快递后,她会判断收件人是谁,然后按照收件人的要求签收,甚至代为付款。这种方案还有一个优势,那就是即使公司里来了新员工(不管多少),前台MM也会在收到寄给新员工的快递后核实并代为签收。

在js中最常见的情形就是在一个ul里有多个li标签,每个li标签绑定一个click事件,最常见的做法是用for循环给每个li绑定click事件,当li不多时还好,但是当li变得很多时就会使我们的性能大大降低,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,提高性能;

实例1

<ul id="ul">
  <li>1</li>
  <li>2</li> 
  <li>3</li>
  <li>4</li>
</ul>

通常用for循环的做法

window.onload = function(){
    var li = document.getElementById("ul").getElementsByTagName("li")
    for(var i=0;i<li.length;i++){
        li[i].onclick = function(){
            alert(123);
        }
    }
}

每个li点击的时候都会查找目标li的位置,可想而知访问dom太频繁,使性能下降。
因此可以利用事件委托来完成

window.onload = function(){
    var ul = document.getElementById("ul");
   ul.onclick = function(){
        alert(123);
    }
}

我们只对ul添加click事件,点击li的时候会向上冒泡使ul也能接收到,这样就只用绑定一个click事件。这个例子是说li操作的是同样的效果,要是每个li被点击的效果都不一样,那么用事件委托还有用吗?看接下来的例子。

实例2

现在要求点击li的时候弹出各自li内的内容,通常的做法如下:

window.onload = function() {
  var li = document.getElementById("ul").getElementsByTagName("li");
  for(var i = 0; i < li.length; i++) {
    li[i].index = i;
    li[i].onclick = function() {
      console.log(li[this.index].innerHTML);
    }
  }
}

第一个例子还有一个问题,就是当我们点击ul的时候也会触发click事件,但是我们希望通过父元素的代理也能知道具体是哪一个节点该如何实现呢?
Event对象提供了一个属性叫target,可以返回事件的目标节点,我们成为事件源,也就是说,target就可以表示为当前的事件操作的dom,但是不是真正操作dom,当然,这个是有兼容性的,标准浏览器用event.target,IE浏览器用event.srcElement,此时只是获取了当前节点的位置,并不知道是什么节点名称,这里我们用nodeName来获取具体是什么标签名,这个返回的是一个大写的,我们需要转成小写再做比较

window.onload = function() {
  var ul = document.getElementById("ul");
  ul.onclick = function(e) {
    var e = e || window.event;
    var target = e.target || e.srcElement;
    if(target.nodeName.toLowerCase() == "li") {
      alert(target.innerHTML)
    }
  }
}

利用target就能知道具体是哪个元素,并且不用考虑for循环闭包的问题。

实例三

另一种情况,如果li内还有其他标签如何判断点击的时候是不是li

<ul id="ul">
        <li>
            <p>11111111111</p>
        </li>
        <li>
            <div>
                22222222
            </div>
        </li>
        <li>
            <span>3333333333</span>
        </li>
        <li>4444444</li>
</ul>

e.target获取的是当前的目标节点,这里有可能是p或div或者span,但是需要判断点击是不是li,解决方法如下:

 var ul = document.getElementById('ul');
    ul.addEventListener('click',function(e){
        var target = e.target;
        while(target !== ul ){
            if(target.tagName.toLowerCase() == 'li'){
                console.log(target.innerText);
                break;
            }
            target = target.parentNode;
        }
    })

通过一个while循环,获取到目标节点后,由于会向上冒泡,将target赋给父节点也就是li,再将各自的内容打印出来,实际上类似递归的思想。