JavaScript的事件委托

8,304 阅读3分钟

一、事件委托

1. 什么是事件委托

在 JavaScript 中,事件委托(delegate)也称为事件托管或事件代理,就是把目标节点的事件绑定到祖先节点上。这种简单而优雅的事件注册方式是基于事件传播过程中,逐层冒泡总能被祖先节点捕获。

这样做的好处:优化代码,提升运行性能,真正把 HTML 和 JavaScript 分离,也能防止出现在动态添加或删除节点过程中注册的事件丢失的现象。

2. 为什么要用事件委托

1)节省监听数,也就是节省内存

设想一个场景,我们要开发一个网页版扫雷程序,网页上有一个id为lei的<div><div>里嵌套有20*20=400个<button>

<div id="lei">
    <button></button>
    <button></button>
    <!-- 一共400个button -->
    <button></button>
</div>

那么,如何为这400个<button>添加click事件呢?

方案1:遍历<div>的全部<button>,给每一个<button>添加click事件,也就是当前页面会同时存在400个事件。(差评)

方案2:为<div>添加一个事件委托,代码如下:

lei.addEventListener('click', (e) => {
    const t = e.target;
    if (t.tagName.toLowerCase() === 'button') {
        console.log('button被点击');
    }
});

上述代码中,事件的监听函数定义在<div>节点,但实际上,它处理的是子节点<button>click事件。显然,方案2比方案1会节约大量的资源。

2)可以监听动态元素

那么,如果刚才的<div>里面的<button>并不是事先写好的HTML,而是js动态生成的呢?

//通过js动态添加button到div
function generateButtons() {
    for (let i = 0; i < 400; i++) {
        const btn = document.createElement('button');
        lei.appendChild(btn);
    }
}

这种情况,只能通过事件委托来监听<div>节点,以实现为<button>添加处理click事件的功能。

二、阻止默认动作

浏览器对于一些事件会触发默认动作,比如点击<a>标签会跳转到href指向的网页,那么如何阻止这个默认动作呢?

在支持addEventListener()的浏览器中,可以通过调用事件对象的preventDefault()方法取消事件的默认操作。IE9之前的IE中,可以通过设置事件对象的returnValue属性为false达到同样的效果。下面一段代码是结合三种技术取消事件:

function cancelHandler(event) {
    var event = event || window.event;//兼容IE
    //取消事件相关的默认行为
    if(event.preventDefault) { //标准技术
        event.preventDefault();
    }
    if(event.returnValue) { //兼容IE9之前的IE
        event.returnValue = false;
    }
    return false; //用于处理使用对象属性注册的处理程序
}

三、阻止事件冒泡

1. 什么是事件冒泡?

举个例子:

<div id="p" style="width: 200px; height: 200px; background-color: red;">
    <div id="c" style="width: 100px; height: 100px; background-color: blue;"></div>
</div>

为上面嵌套的两个div,分别添加onclick事件

p.onclick = () => console.log('p is clicked');
c.onclick = () => console.log('c is clicked');

1)在鼠标点击外层红色的<div>时,控制台输出

p is clicked

2)在鼠标点击内层蓝色的<div>时,控制台输出

c is clicked

p is clicked

2. 事件冒泡的概念

浏览器从用户点击的按钮从下往上遍历至 window,逐个触发事件处理函数。

W3C 事件模型/事件机制:对每个事件先捕获再冒泡。

3. 如何阻止事件冒泡

1)event.stopPropagation()

我们来改写刚才js代码的c.onclick部分

c.onclick = (event) => {  
  console.log('c is clicked');
  event.stopPropagation();
};

在c的onclick代码加上一个参数event,并在代码最后一行加上event.stopPropagation();

通过这种方式,就可以达到阻止事件冒泡的目的:控制台输出c is clicked后不会再输出p is clicked。

注意,event.stopPropagation()并不会阻止默认动作。

2)return false

c.onclick = () => {  
  console.log('c is clicked');
  return false;
};

通过return false的方式,既阻止了事件冒泡,也阻止了默认行为。