事件委托的概念和原理

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43995925/article/details/94597080

事件委托优点

  1. 减少DOM操作的,减少浏览器的重绘(repaint)和重排(reflow),从而提高性能;
  2. 减少内存空间的占用率,因为每一个函数都是一个对象,对象越多,内存占有率就越大,自然性能就越差,使用事件委托,只需要在其父元素中定义一个事件就可以。
  3. 适合事件委托的事件有:click,mousedown,mouseup,keydown,keyup,keypress
  4. 可以方便地动态添加和修改元素,不需要因为元素的改动而修改事件绑定。

为什么要用事件委托

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

  1. 操作DOM次数过多,造成浏览器的重排和重绘就越多;
  2. 每个事件都是一个对象,事件处理程序越多,占用的内存越多,影响前端性能;

事件委托(事件代理)原理——事件冒泡

事件委托利用事件冒泡(从最深的节点开始,然后逐步向上传播事件)只在他们的父元素上指定一个事件处理程序,就可以管理某一类型的的所有事件。

事件委托的实现

事件源Event对象提供了一个属性叫target,可以返回事件的目标节点,标准浏览器用ev.target,IE浏览器用event.srcElement

1,父元素绑定事件

<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) {
                var ev = ev || window.event;
                var target = ev.target || ev.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;
                    }
                }
            }   
        }
//或者使用oBox.addEventListener(时间类型,函数,默认false为时间冒泡),下面我会讲到

上述target只是获得了当前节点位置,但不知道具体节点名称,所以使用nodeName来获取标签名,这里获得但标签名是大写INPUT

除来获取标签名,这里也可以获取当前点击标签但其他属性,例如:ev.target.id, ev.target.value, ev.target.type.

这里用父级div做事件处理,当input被点击时,由于冒泡原理,事件就会冒泡到div上,因为div上有点击事件,所以事件就会触发


接下来我们再看一个例子  addEventListener

<div id="div1">
    点击
</div>

<script>
    window.onload = function(){
        let div1 = document.getElementById('div1');
        div1.onclick = function(){
            console.log('打印第一次')
        }
        div1.onclick = function(){
            console.log('打印第二次')
        }
    }
</script>

 

可以看到第二个点击注册事件覆盖了第一个注册事件,只执行了console.log('打印第二次');


addEventListener(type,listener,useCapture)实现

  • type: 必须,String类型,事件类型
  • listener: 必须,函数体或者JS方法
  • useCapture: 可选,boolean类型。指定事件是否发生在捕获阶段。默认为false,事件发生在冒泡阶段
window.onload = function(){
        let div1 = document.getElementById('div1');
        div1.addEventListener('click',function(){
            console.log('打印第一次')
        })
        div1.addEventListener('click',function(){
            console.log('打印第二次')
        })
    }

我们看一下效果


可以看到两个注册事件都成功触发了。 useCapture是事件委托的关键。


事件捕获和事件冒泡机制

事件捕获:当一个事件触发后,从Window对象触发,不断经过下级节点,直到目标节点。在事件到达目标节点之前的过程就是捕获阶段。所有经过的节点,都会触发对应的事件

事件冒泡:当事件到达目标节点后,会沿着捕获阶段的路线原路返回。同样,所有经过的节点,都会触发对应的事件

 

例子:假设有body和body节点下的div1均有绑定了一个注册事件.
效果:
    当为事件捕获(useCapture:true)时,先执行body的事件,再执行div的事件
    当为事件冒泡(useCapture:false)时,先执行div的事件,再执行body的事件

//当useCapture为默认false时,为事件冒泡
<body>
    <div id="div1"></div>
</body>

<script>
    window.onload = function(){
        let body = document.querySelector('body');
        let div1 = document.getElementById('div1');
        body.addEventListener('click',function(){
            console.log('打印body')
        })
        div1.addEventListener('click',function(){
            console.log('打印div1')
        })
    }
</script>

//结果:打印div1  打印body

//当useCapture为true时,为事件捕获
<body>
    <div id="div1"></div>
</body>

<script>
    window.onload = function(){
        let body = document.querySelector('body');
        let div1 = document.getElementById('div1');
        body.addEventListener('click',function(){
            console.log('打印body')
        },true)
        div1.addEventListener('click',function(){
            console.log('打印div1')
        })
    }
</script>

//结果:打印body   打印div1


接下来我们看一下事件委托和新增节点绑定事件之间的关系

<body>
    <div id="div">
        <div class="div1">div1</div>
        <div class="div2">div2</div>
    </div>
</body>

<script>
    window.onload = function(){
        let div = document.getElementById('div');

        div.addEventListener('click',function(e){
            console.log(e.target)
        })

        let div3 = document.createElement('div');
        div3.setAttribute('class','div3')
        div3.innerHTML = 'div3';
        div.appendChild(div3)
    }
</script>

依次点击子div:

虽然没有给div1和div2添加点击事件,但是无论是点击div1还是div2,都会打印当前节点。因为其父级绑定了点击事件,点击div1后冒泡上去的时候,执行父级的事件。

这样无论后代新增了多少个节点,一样具有这个点击事件的功能。

 

展开阅读全文

没有更多推荐了,返回首页