来个事件委托看看

91 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

我们知道对于js的事件来说存在三个个阶段,一是事件捕获阶段,二是目标阶段,三是事件冒泡阶段,当标签又多层结构的时候,事件触发的时候,事件由外层向内层传递我们称之为事件捕获阶段,事件由内向外传递的过程我们称之为事件冒泡;原生js我们使用stopPropagation来阻止事件冒泡,vue中可以用事件修饰符stop来阻止事件冒泡,对于事件冒泡我们有个很经常的用途就是用来进行事件委托处理。

什么是事件委托

如ul>(li*100)这个时候要给每一个li添加点击事件,我们只需要给ul添加点击事件,当点击li的时候通过事件冒泡,就可以触发事件了。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <ul>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
        <li></li>
    </ul>
</body>
</html>

如上面这种文档结构,如果我们要给li添加点击事件,那么我们就要一个个的进行循环添加

    let liArr = document.getElementsByTagName('li');
    liArr.forEach(element => {
        element.onclick=function(){
            dosomething();
            。。。。
        }
    });

这样肯定是能实现我们的需求的,但是带来的问题就是每一个li上都绑定了一个事件,十分消耗性能,同时如果我的li是动态改变的,那么新增的li我又要重新进行绑定。

事件委托进行实现

事件冒泡就是事件在本元素触发后,会像泡泡一样一层一层的向外扩展,那么在事件触发元素的父元素也是可以监听到的,所以我们可以在父元素那里统一进行监听后处理:

    let ulArr = document.getElementsByTagName('ul');
    ulArr[0].onclick=function(e){
        if(e.target.tagName.toLowerCase()=='li'){
            doSomething();
            。。。
        }
    }

这样当li的点击事件触发的时候,就会通过事件冒泡的形式被ul的监听到,然后触发响应的回调函数进行处理,可以看到这样我们原本每一个li都要进行绑定事件的,现在只有其公共父元素ul需要绑定,大大的优化了性能,同时如果后续li再动态增加也没有关系,因为其层级关系并没有改变。

优化

但是这样写还是有一些问题存在,假设我的li并不是单纯的li,里面还包含有其他的元素,那么其他元素所触发的点击事件就不会触发父元素的回调函数了,因为此时触发事件的元素已经不是我们的li元素了。

解决方法其实也不难,也就是我们在判断触发元素的时候,还要判断一下触发事件元素的父元素中是否存在li元素,如果存在那么就说明,其实li中的内部元素,其触发的事件应该也是要归属于我们的li的,代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <ul>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
        <li>
            <span>测试</span>
        </li>
        <li>1</li>
        <li>1</li>
        <li>1</li>
    </ul>
</body>
</html>
<script>
    let ulArr = document.getElementsByTagName('ul');
    ulArr[0].onclick=function(e){
        let even=e.target;
        while(!even.tagName.toLowerCase()=='li'){
            if(ulArr[0]===even){
                even=null;
                break;
            }
            even=even.parentNode;
        }
        even&&console.log("doSomeThing...");
    }
</script>

可以看到改进后的版本会对目标元素进行判断,如果是li里面的元素我们可以根据要求进行判断处理,这样就避免不必要的触发,和本应该有却没有的事件触发了。