简述事件委托

167 阅读2分钟

"过多事件处理程序"的解决方案

场景一:你要给100个按钮添加点击事件,咋办?

  • “过多事件处理程序“的解决方案是使用事件委托。事件委托利用事件冒泡,可以只使用一个事件处理程序来管理一种类型的事件。例如,click事件冒泡到document。这意味着可以为整个页面指定一个onclick事件处理程序,而不用为每个可点击元素分别指定事件处理程序。比如有以下HTML:

    <ul id="myLinks"
        <li id="goSomewhere">Go Somewhere</li>
        <li id="doSomething">Do Something</li>
        <li id="sayHi">Say hi</li>
    </ul>
    

    这里的HTML包含3个列表项,在被点击时应该执行某个操作。对此,通常的做法是像这样指定3个事件处理程序:

    let item1 = document.getElementById("goSomewhere");
    let item2 = document.getElementById("doSomething");
    let item3 = document.getElementById("sayHi");
    
    item1.addEventListener("click", (event) => {
            location.href = "https://www.baidu.com";
    });
    
    item2.addEventListener("click", (event) => {
            document.title = "I am changing the document's title";
    });
    
    item3.addEventListener("click", (event) => {
    console.log("hi");
    })
    

    如果对页面中所有需要使用onclick事件处理程序的元素都如法炮制,结果就会出现大片雷同只为指定事件处理程序的代码,例如如果有100个button元素呢。使用事件委托,只要给所有元素共同的祖先节点添加一个事件处理程序,就可以解决问题。比如:

    let list = document.getElementById("myLinks");
    
    list.addEventListener("click", (event) => {
        let target = event.target;
        
        switch(target.id) {
            case "goSomewhere":
                location.href="https://www.baidu.com";
                break;
            case "doSometing":
                document.title = "I am changing the document's title";
                break;
            case "sayHi":
                console.log("hi");
                break;
        }
    });
    
  • 另一个栗子:通常用于为许多相似的元素添加相同的处理

    <button data-toggle-class="subscribe">Show the subscription form</button>
    
    <form class="subscribe" hidden>Your mail: <input type="email" /></form>
    
    <form class="subscribe" hidden>
        Your password: <input type="password" />
    </form>
    
    <script>
        document.addEventListener("click", function (event) {
            let cls = event.target.dataset.toggleClass;
            if (!cls) return;
            
            let elems = document.getElementsByClassName(cls);
            
            for (let i = 0; i < elems.length; i++) {
                elems[i].hidden = !elems[i].hidden;
            }
        });
    </script>
    

监听动态元素的解决方案

场景二:你要监听目前不存在的元素的点击事件,咋办?

  • 监听祖先,等点击的时候看看是不是我想要监听元素即可。

    <body>
        <div id="div1"></div>
    </body>
    
    <script>
        setTimeout(() => {
            const btn = document.createElement("button");
            btn.textContent = "Click me";
            div1.appendChild(btn);
        }, 1000);
        
        div1.addEventListener("click", (event) => {
            let t = event.target;
            if (t.tagName.toLowerCase() === "button") {
                console.log("button clicked!");
            }
        });
    </script>
    

小结

  • 算法
    1. 在容器(container)上放一个处理程序
    2. 在处理程序中 —— 检查源元素 event.target
    3. 如果事件发生在我们感兴趣的元素内,那么处理该事件。
  • 好处
    1. 简化初始化并节省内存:无需添加许多处理程序。
    2. 可以监听动态元素。
  • 局限性
    1. 首先,事件必须冒泡。而有些事件不会冒泡。此外,低级别的处理程序不应该使用 event.stopPropagation()
  • 推荐阅读:
    现代JavaScript教程 - 事件委托