【javascript】事件委托/事件代理的理解 从0开始逐步理解

事件委托属于前端必会知识点,在实际操作中也会常用到。本小笨才以为,为了避免实操中写出bug,仅仅简单地会使用不够,还需要了解它的概念,原理,使用场景,优缺点,兼容性等等。所以今天来钻研一下。并记录下来供大家一起学习讨论。

事件委托的定义:

事件委托/代理的原理是DOM元素的事件冒泡。就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
参考
为了方便理解,摘一个恰当的例子:

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

这里其实还有2层意思的:

  • 第一,现在委托前台的同事是可以代为签收的,即程序中的现有的dom节点是有事件的。
  • 第二,新员工也是可以被前台MM代为签收的,即程序中新添加的dom节点也是有事件的。
了解完事件委托的概念,来了解下什么是事件冒泡?

一个事件触发后,会在子元素和父元素之间传播。DOM2.0模型将事件处理流程分为三个阶段:一、事件捕获阶段,二、事件目标阶段,三、事件冒泡阶段
在这里插入图片描述

  • 事件捕获阶段:从window对象传导到目标节点(顶层逐层向里传)称为“捕获阶段”(capture phase),捕获阶段不会响应任何事件
  • 目标阶段:当到达目标元素之后,执行目标元素相应的处理函数。如果没有绑定监听函数,就不执行。
  • 冒泡阶段:从目标节点往顶层元素传(从里向外),称为“冒泡阶段”。途中如果有节点绑定了相应的事件处理函数,这些函数也会被触发【所有有时候需要阻止事件冒泡】。

事件代理/委托即是利用事件冒泡的机制把里层所需要响应的事件绑定到外层

事件委托的优点

  1. 可以大量节省内存占用,减少事件注册,优化整体运行性能。比如在ul上代理所有li的click事件【将li元素的点击事件绑定到它的父元素ul身上,执行事件的时候再去匹配判断目标元素。】
    不需要为每个元素都添加监听函数。对于同一个父节点下面类似的子元素,可以通过将事件委托给父元素的监听函数的方式来处理。
  2. 当新增子对象时无需再次对其绑定(动态绑定事件)。因为事件是绑定在父层,和目标元素的增减没有关系,执行到目标元素是在真正响应执行事件的过程中去匹配的,所以事件委托可以减少很多重复工作。
  3. js和DOM之间的关联变少了,这样就减少了因循环引用带来的内存泄露问题发生的概率。

文档参考

事件委托使用场景和常见问题

  1. 如果我想让事件代理的效果跟直接给节点绑定事件的效果一样怎么办?【比如说只有点击li时才触发事件】
  2. 如果有不确定数量的子元素都需要某一类相同的事件监听,就可以将事件委托给父元素,通过事件冒泡原理,将监听事件相应到目标元素上。

讲js事件委托之前,先来了解addEventListener()方法和event对象的属性target。

1.HTML DOM addEventListener() 方法
  • addEventListener() 方法向指定元素绑定事件监听。

  • addEventListener() 支持谷歌,IE9及以上,火狐等浏览器。对于这些不支持该函数的浏览器,可以使用 attachEvent() 方法来绑定事件监听

  • 提示: 使用 removeEventListener() 方法来移除
    addEventListener() 方法添加的事件句柄。

2. 原生js事件委托通过e.target获取事件触发的对象来完成委托
  • event对象提供的属性target,可以返回事件的目标节点【事件源】。
  • 标准浏览器用ev.target,IE浏览器用event.srcElement。
<srcipt>
	window.onload = function{
		var ul = document.getElementById('ull');
		ul.onclick = function(ev) {//给父元素ul绑定一个点击事件
			var ev = ev || window.event;//为了兼容IE
			var target = ev.target || ev.srcElement;//为了兼容IE
			//此时的target就相当于是事件源,即目标节点
			if(target.nodeName.toLowerCase() == 'li'){//通过target.对目标节点进行操作
				alert('成功');
			}
		}
	}

事件委托demo:

js事件委托demo1:参考
<ul id="myLinks">
  <li id="goSomewhere">Go somewhere</li>
  <li id="doSomething">Do something</li>
  <li id="sayHi">Say hi</li>
</ul>


var item1 = document.getElementById("goSomewhere");
var item2 = document.getElementById("doSomething");
var item3 = document.getElementById("sayHi");
document.addEventListener("click", function (event) {
    //var target = event.target;
    var ev = ev || window.event;//兼容IE
    var target = ev.target || ev.srcElement;//兼容IE
    switch (target.id) {
        case "doSomething":
          document.title = "事件委托";
          break;
        case "goSomewhere":
          location.href = "http://www.baidu.com";
          break;
        case "sayHi": alert("hi");
          break;
     }
}
js事件委托demo2:参考
<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>

window.onload = function(){
     var oBox = document.getElementById("box");
     oBox.onclick = function (ev) {//将事件委托给父元素box,事件绑定在父元素上
         var ev = ev || window.event;//兼容IE
         var target = ev.target || ev.srcElement;//兼容IE
         if(target.nodeName.toLocaleLowerCase() == 'input'){
                    switch(target.id){
                        case 'add' ://根据ID名定位到真正的目标元素触发相应事件
                            alert('添加');
                            break;
                        case 'remove' :
                            alert('删除');
                            break;
                        case 'move' :
                            alert('移动');
                            break;
                        case 'select' :
                            alert('选择');
                            break;
                    }
          }
      } 
}

事件委托能让动态添加的子元素也有事件

上面两个demo都是在现有的dom节点下的操作,如果要给新添加的dom元素绑定事件呢?怎么处理?如下demo:

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

//事件委托,添加的子元素也有事件  给父元素绑定事件,当元素为li时,鼠标移入背景变红,移出变白
oUl.onmouseover = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    if(target.nodeName.toLowerCase() == 'li'){
          target.style.background = "red";
	}
};
oUl.onmouseout = function(ev){
     var ev = ev || window.event;
     var target = ev.target || ev.srcElement;
     if(target.nodeName.toLowerCase() == 'li'){
          target.style.background = "#fff";
      }
};
//添加新节点  点击按钮添加的新节点li,也会有移入移出的绑定事件
Btn.onclick = function(){
    var oLi = document.createElement('li');
    oUl.appendChild(oLi);
;

通过上面,我们可以发现,事件委托不需要遍历元素的子节点,就能让子节点也有事件。只需要给父级元素添加事件就好了,其他都是在js执行,这样可以大大的减少dom操作,这是事件委托的精髓所在


参考事件委托不仅仅只是为处理一种dom操作类型,还可以进行增删改查等功能:

<body>
    <div id="events">
        <input type="button" id='addHandle' value='增加'>
        <input type="button" id='deleteHandle' value='减少'>
    </div>
    <div id="content">

    </div>
</body>
<script src='http://code.jquery.com/jquery-2.1.1.min.js'></script>
<script>
    let events = document.getElementById('events');
    let content = document.getElementById('content');
    events.onclick=function(e){//让两个事件都绑定在同一个dom元素上,减少了内存,降低了性能。
        let ev = e || window.event;
        let target = ev.target || ev.srcElement;
        if(target.nodeName.toLowerCase()=='input'){
            switch(target.id){
                case 'addHandle':
                return addEvent();
                break
                case 'deleteHandle':
                return deleteEvent();
                break
            }
        }
    }
    function addEvent(){
        let add = document.createElement('p')
        add.innerHTML = '点击按钮新增'
        content.appendChild(add)
    }
    function deleteEvent(){
        let del = document.createElement('p')
        del.innerHTML = '点击按钮删除'
        content.appendChild(del)
    }
</script>

jquery事件委托的操作:

$('#events').on('click','input',(e)=>{
        let target = $(e.target);
        switch(target[0].id){
                case 'addHandle':
                return addEvent();
                break
                case 'deleteHandle':
                return deleteEvent();
                break
        }
    })
    function addEvent(){
        $('#content').append($('<div>这是增加按钮</div>'))
    }
    function deleteEvent(){
        $('#content').append($('<div>这是删除按钮</div>'))
    }

再补充jquery事件委托

jquery事件委托:
相较于js事件委托,jquery事件委托的优势:执行事件委托的时候只有子元素会触发事件函数,而代为执行的父元素不会触发事件函数;(注意这里的事件委托用的方法on,如果用bind方法父元素会触发事件函数)
demo1:

<body>
    <div id="nodes">
      <P class="node_p">第一个p</P>
      <P class="node_p">第二个p</P>
      <P class="node_p">第三个p</P>
    </div>
    <button>点击加一</button>
</body>
let inner = 33;
//这里nodes节点下所有标签为p的子节点都被赋予事件处理函数;
$('#nodes').on('click','p',function(e){
    let target = $(e.target)
    target.css('backgroundColor','red')
})
$('button').click(()=>{
   inner++;
   $('#nodes').append($('<p>我是新增加的p标签'+inner+'</p>'))
})
jQuery事件delegate()实现事件委托 参考

delegate() 方法为指定的元素(属于被选元素的子元素)添加一个或多个事件处理程序。

格式:$(selector).delegate(childSelector, event, data, function)

参数 描述
childSelector 必需,规定要附加事件处理程序的一个或多个子元素。
event 必需,规定附加到元素的一个或多个事件。由空格分隔多个事件值。必须是有效的事件。
data 可选,规定传递到函数的额外数据。
function 必需,规定当事件发生时运行的函数。

<!DOCTYPE html>
<html> 
<head>
  <meta charset="utf-8">
  <script src="http://lib.sinaapp.com/js/jquery/2.0.2/jquery-2.0.2.min.js"></script>
</head> 
<body>
  <ul id="myLinks">
    <li id="goSomewhere">Go somewhere</li>
  </ul>
  <script>
    $(document).ready(function () {
      $("#myLinks").delegate("#goSomewhere", "click", function () {
        location.href = "http://www.baidu.com";
      });
    });
  </script>
</body>
</html>

适合用事件委托的事件:

click,mousedown,mouseup,keydown,keyup,keypress。(所有用到按钮的事件,多数的鼠标事件和键盘事件)

值得注意的是,mouseover和mouseout虽然也有事件冒泡,但是处理它们的时候需要特别注意,因为需要经常计算它们的位置,处理起来不太容易。

不适合的就有很多了,举个例子,mousemove,每次都要计算它的位置,不好把控。
再比如说focus,blur之类的,本身就没用冒泡的特性,自然就不能用事件委托了。

访问DOM的次数越多,引起浏览器重绘和重排的次数越多,就会延迟整个页面的交互就绪时间。所以性能优化的主要思想之一就是减少DOM操作

每个函数都是一个对象,是对象就会占用内存,所以通过事件委托,可以大大降低内存占用率,也能大大降低操作DOM的交互次数,从而提高性能。


以上内容出自各参考文档,至此对于事件代理有了更多的理解,感谢。

以上内容如有不当或补充请小天才们大方留言,这会是我继续热爱写笔记的小小动力之一,小笨才先此谢过。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值