felix-cao / Blog

A little progress a day makes you a big success!
31 stars 4 forks source link

JavaScript 事件委托/代理(Event Delegation) #133

Open felix-cao opened 5 years ago

felix-cao commented 5 years ago

一、概述

1.1、概念

事件委托或者叫事件代理,英文叫 Event DelegationJavaScript 高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

通俗点讲,事件委托就是利用事件冒泡原理,把一个或一组元素的事件委托到它的父层或更外层元素上去指定事件处理程序

概念理解起来还是有点生涩的,前辈们在讲事件委托基本上都用了同一个例子,就是取快递来解释这个现象,认真领会一下是非常有道理的:

有三个同事预计会在周一收到快递。为签收快递,有两种办法:

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

结合 DOM 去理解:

1.2、为什么要用事件委托:

DOM 编程是有交互的,交互就有事件处理程序,一个DOM节点事件还好处理,如果有很多的DOM需要添加事件处理呢?比如我们有100个 li,每个 li 都有相同的 click 点击事件,常规的做法我们可能会用 for 循环的方法,来遍历所有的 li,给每个 li添加事件,这是不是与 都站在门口等快递如出一辙

《浏览器的 reflow 和 repaint》,操作 DOM 会引起浏览器的回流重绘,二者有很大的性能开销,所以要减少DOM 操作。

JavaScript 中一切皆对象,对象就会占用内存,对象越多,内存占用率就越大,自然性能就越差了,比如上面的100个 li,就要占用100个内存空间,如果是1000个,10000个呢,那只能说呵呵了,如果用事件委托,那么我们就可以只对它的父级(如果只有一个父级)这一个对象进行操作,这样我们就需要一个内存空间就够了,是不是省了很多,自然性能就会更好。

1.3、原理:

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

二、事件委托的实现

假设有下面的代码:

<ul id="ul">
    <li id="li1">li1</li>
    <li id="li2">li2</li>
    <li id ="li3">li3</li>
    <li id="li4">li4</li>
</ul>

实现功能是点击 li,控制台中打印 li1234

2.1、循环给节点加事件

window.onload = function() {
  var Ul = document.getElementById('ul');
  var LI = Ul.getElementsByTagName('li');
  for(var i=0; i < LI.length; i++) {
    LI[i].onclick = function() {
      console.log('li1234');
    }
  }
}

这段代码通过循环给每个 li 添加鼠标点击事件,其 DOM 操作是这样的:先找到 ul 节点,遍历该ul 下的 li 节点,然后点击 li 的时候,再知道目标的 li 的位置,执行最后的操作,每次点击都必须找一次 li

2.2、委托给他的父级节点

《JavaScript 事件流》中,我们知道事件冒泡是从事件发生所在的节点开始,然后逐级传播到DOM根节点,是一种自下往上的顺序。因此我们可以在父节点ul绑定事件。 下面在父节点 ul 上绑定事件

window.onload = function() {
  document.getElementById('ul').onclick = function() {
     console.log('li1234');
  }
}

li 被点击时,由于冒泡原理,事件就会冒泡到 ul 上,因为ul上有点击事件,所以事件就会触发,但是这样有个问题:我们并不知道点击的是哪一个具体的 li

2.3、Event.target 属性

事件对象Event提供了一个target属性,可以返回触发此事件的目标节点,即事件源,简单点说,target就可以表示为当前的事件操作的dom

window.onload = function() {
  document.getElementById("ul").onclick = function(ev) {
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement; // IE浏览器用event.srcElement
    if (target.nodeName.toLowerCase() == 'li') { // nodeName 是节点名称
      console.log('li1234');
      console.log(target.innerHTML);
    }
  }
}

三、新增节点的事件委托

如下代码,要求鼠标悬停在 li 上, li 背景变为红色。

<input type="button" id="btn" value="新增" />
<ul id="ul">
   <li>li1</li>
   <li>li2</li>
   <li>li3</li>
   <li>li4</li>
</ul>
window.onload = function() {
  var btn = document.getElementById("btn");
  var Ul = document.getElementById("ul");
  var LI = oUl.getElementsByTagName('li');

  // 事件委托,添加的子元素也有事件
  Ul.onmouseover = function(ev) {
      var ev = ev || window.event;
      var target = ev.target || ev.srcElement;
      if(target.nodeName.toLowerCase() === 'li') {
         target.style.background = "red";
      }

  };

  // 添加新节点
 btn.onclick = function(){
      var nLI = document.createElement('li');
      nLI.innerHTML = 'li' + LI.length;
       Ul.appendChild(nLI);
  };
}