JS 事件的委托

1.概述
Q:什么是事件委托?
A:利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件,即将子类需要执行的事件委托给父类完成

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

这里有两层意思
第一,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的;
第二,新员工也是可以被前台代为签收的,即程序中新添加的dom节点也是有事件的。

2.为什么要用事件委托
一般来说,dom需要有事件处理程序,我们都会直接给它设事件处理程序就好了,那如果是很多的dom需要添加事件处理呢?比如我们有50个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,那这么做会存在什么影响呢?

在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间,这就是为什么性能优化的主要思想之一就是减少DOM操作的原因;如果要用事件委托,就会将所有的操作放到js程序里面,与dom的操作就只需要交互一次,这样就能大大的减少与dom的交互次数,优化代码性能

每个函数都是一个对象,是对象就会占用内存,对象越多,内存占用率就越大,性能也就越差。比如上面的50个li,就要占用50个内存空间,如果是100个,1000个呢。如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就只需要一个内存空间,自然性能就更好。

优点

  1. 减少事件注册,节省内存
  • 在table上代理所有td的click事件
  • 在ul上代理所有li的click事件
  1. 简化了dom节点更新时,相应事件的更新
  • 不用在新添加的li上绑定click事件
  • 当删除某个li时,不用移解绑上面的click事件

缺点

  • 事件委托基于冒泡,对于不冒泡的事件不支持。
  • 层级过多,冒泡过程中,可能会被某层阻止掉。
  • 理论上委托会导致浏览器频繁调用处理函数,虽然很可能不需要处理。所以建议就近委托,比如在table上代理td,而不是在document上代理td。
  • 把所有事件都用代理就可能会出现事件误判。比如,在document中代理了所有button的click事件,另外的人在引用改js时,可能不知道,造成单击button触发了两个click事件。

3.原理

事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?
就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件

4.如何实现

例1:li操作的是同样的效果
设置样式:

.lilist {
     list-style: none;
     width: 200px;
     line-height: 35px;
     border: 1px solid black;
     margin: 2px 0;
     text-align: center;
}

子节点实现相同的功能:

<ul class="menu">
    <li class="lilist">菜单1</li>
    <li class="lilist">菜单2</li>
    <li class="lilist">菜单3</li>
    <li class="lilist">菜单4</li>
    <li class="lilist">菜单5</li>
    <li class="lilist">菜单6</li>
    <li class="lilist">菜单7</li>
    <li class="lilist">菜单8</li>
</ul>

实现功能是点击li,改变其背景颜色:
(1)一般写法

var liinfo = document.getElementsByClassName("lilist");
for (var i = 0; i < liinfo.length; i++) {
    liinfo[i].onclick = function () {
         this.style.backgroundColor = "silver";
    }
}

首先要找到ul,遍历li,然后点击li时,又要找一次目标的li的位置,才能执行最后的操作,每次点击都要找一次li。

(2)事件委托

var menu = document.getElementsByClassName("menu")[0];
menu.onclick = function () {
     menu.style.backgroundColor="silver";
}

用父级ul做事件处理,当li被点击时,由于冒泡原理,事件就会冒泡到ul上,因为ul上有点击事件,所以事件就会触发。但当点击ul时,也会触发,那么问题来了,若让事件代理的效果跟直接给节点的事件效果一样怎么办?
比如说只有点击li才会触发,因此需使用以下方法。

(3)使用event.target

var menu = document.getElementsByClassName("menu")[0];
menu.onclick = function (event) {
    var target = event.target || event.srcElement;
    console.log(target);  //<li class="lilist">菜单1</li>点击哪个li会出现相对应的li
    console.log(target.nodeName);  //LI
    /*判断是否拿到li*/
    if (target.nodeName.toLowerCase() == "li") {  //将LI转为li
        target.style.backgroundColor = "silver";
    }
}

event.target:返回事件的目标节点,称为事件源;event事件的执行参数target目标元素,可以表示为当前的事件操作的dom,但不是真正操作dom;此时只是获取了当前节点的位置,并不知道是什么节点名称,因此需用nodeName(当前元素的节点名称)来获取具体是什么标签名,这个返回的是一个大写的,需转成小写再做比较。

例2:每个li被点击的效果都不一样

<div id="box">
    <input type="button" id="add" value="添加">
    <input type="button" id="remove" value="删除">
    <input type="button" id="move" value="移动">
    <input type="button" id="select" value="选择">
</div>

(1)一般写法

window.onload=function(){
     var Add = document.getElementById("add");
     var Remove = document.getElementById("remove");
     var Move = document.getElementById("move");
     var Select = document.getElementById("select");
     Add.onclick = function(){
         alert("添加");
     };
     Remove.onclick = function(){
         alert("删除");
     };
     Move.onclick = function(){
         alert("移动");
     };
     Select.onclick = function(){
         alert("选择");
     }
}

(2)事件委托

window.onload=function(){
    var box = document.getElementById("box");
    box.onclick = function (event) {
       var target = event.target || event.srcElement;
       if(target.nodeName.toLocaleLowerCase() == "input"){
              switch(target.id){
                   case "add" :alert("添加");
                           break;
                   case "remove" :alert("删除");
                           break;
                   case "move" :alert("移动");
                           break;
                   case "select" :alert("选择");
                           break;
              }
         }
     }
}

例3:添加新的节点

<input type="button" id="btn" value="添加">
<ul id="ul1">
    <li>111</li>
    <li>222</li>
    <li>333</li>
    <li>444</li>
</ul>

实现功能:移入li,li变红,移出li,li变白,这么一个效果,然后点击按钮,可向ul中添加一个li子节点

(1)一般写法

window.onload = function () {
     var btn = document.getElementById("btn");
     var ul1 = document.getElementById("ul1");
     var lilist = ul1.getElementsByTagName("li");
     var num = 4;
     //鼠标移入变红,移出变蓝
     for (var i = 0; i < lilist.length; i++) {
          lilist[i].onmouseover = function () {
              this.style.background = "red";
          };
          lilist[i].onmouseout = function () {
              this.style.background = "blue";
          }
      }
      //添加新节点
      btn.onclick = function () {
           num++;
           var lilist = document.createElement("li");
           lilist.innerHTML = 111 * num;
           ul1.appendChild(lilist);
      };
}

实现结果:新增的li是没有事件的,说明添加子节点的时候,事件没有一起添加进去

(2)将for循环用一个函数包起来,命名为move

window.onload = function(){
     var btn = document.getElementById("btn");
     var ul1 = document.getElementById("ul1");
     var lilist = ul1.getElementsByTagName("li");
     var num = 4;
     function move () {
          //鼠标移入变红,移出变白
          for(var i=0; i<lilist.length;i++){
              lilist[i].onmouseover = function(){
                  this.style.background = "red";
              };
              lilist[i].onmouseout = function(){
                  this.style.background = "blue";
              }
           }
      }
      move ();
      //添加新节点
      btn.onclick = function(){
           num++;
           var lilist = document.createElement("li");
           lilist.innerHTML = 111 * num;
           ul1.appendChild(lilist);
           move ();
      };
}

实现结果:功能可以实现,但实际上又增加了一个dom操作,在优化性能方面是不可取的

(3)事件委托

window.onload = function(){
     var btn = document.getElementById("btn");
     var ul1 = document.getElementById("ul1");
     var lilist = ul1.getElementsByTagName("li");
     var num = 4;
     //事件委托,添加的子元素也有事件
     ul1.onmouseover = function(event){
          var target = event.target || event.srcElement;
          if(target.nodeName.toLowerCase() == "li"){
              target.style.background = "red";
          }
     };
     ul1.onmouseout = function(event){
           var target = event.target || event.srcElement;
           if(target.nodeName.toLowerCase() == "li"){
               target.style.background = "blue";
           }
     };
     //添加新节点
     btn.onclick = function(){
           num++;
           var lilist = document.createElement("li");
           lilist.innerHTML = 111 * num;
           ul1.appendChild(lilist);
     };
}

实现结果:新添加的子元素带有事件效果,当用事件委托的时候,不需要遍历元素的子节点,只需给父级元素添加事件,其他都是在js里面执行,这样可以大大的减少dom操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值