说说如何使用事件委托进行性能优化

简介: 说说如何使用事件委托进行性能优化

前言

JavaScript 事件流描述的是从页面中接收事件的顺序,这个流程从最外层的对象开始(通常是 window 对象),然后经过特定的元素,直到达到触发事件的目标元素。理解事件流对于理解事件处理机制和实现事件委托是非常重要的。

正文

事件机制是指当特定的操作(如点击按钮、移动鼠标等)在DOM元素上发生时,会触发相应的事件。JavaScript通过监听事件并绑定对应的处理函数来响应用户的操作,对用户的交互做出响应。

事件流有三个阶段:

1. 捕获阶段(Capture Phase)

事件从最外层的对象(通常是 window 对象)开始,然后向下穿过DOM树,直到达到触发事件的目标元素。在捕获阶段,事件是从外向内传播的。

例如:window -> document -> html -> body -> div

2. 目标阶段(Target Phase)

事件已经到达了触发它的目标元素。这是事件处理函数实际执行的阶段。

3. 冒泡阶段(Bubbling Phase)

事件从目标元素开始向上冒泡,经过DOM树上的父元素,直到达到最外层的对象(通常是 window 对象)。在冒泡阶段,事件是从内向外传播的。

例如:div -> body -> html -> document -> window。

这是我从网上找来的一张图,相信大家看完之后可以更好的理解事件捕获和事件冒泡。

好的,相信大家看完事件流的概念之后,对事件流这个机制有一定的理解,接下来我们来看看事件捕获和事件冒泡的例子。这里我们使用addEventListener方法来绑定捕获和冒泡事件。

事件绑定

element.addEventListener(event, handler, useCapture);

addEventListener有三个参数,我们来介绍一下:

1. event(事件类型)

这是一个字符串,指定要监听的事件类型,如 "click", "mouseover", "keydown" 等。

2. handler(事件处理函数)

这是一个函数,当指定的事件类型在指定的元素上触发时,该函数就会被调用。事件对象(Event 对象)将作为参数传递给处理函数,你可以使用这个对象来获取事件的详细信息,如事件目标、鼠标坐标、键盘按键等。

3. useCapture(是否使用捕获阶段)

这是一个可选的布尔值,决定事件是在捕获阶段(true)还是冒泡阶段(false)进行处理。

  • true:使用捕获阶段进行事件处理。
  • false:使用冒泡阶段进行事件处理。

默认值:如果不提供 useCapture 参数,它默认为 false,即在冒泡阶段处理事件。

<style>
        #app{
            width: 400px;
            height: 400px;
            background-color: aqua;
        }
        #wrap{
            width: 200px;
            height: 200px;
            background-color: blueviolet;
        }
        #box{
            width: 100px;
            height: 100px;
            background-color: #000;
        }
    </style>
    <div id="app">
        <div id="wrap">
            <div id="box"></div>
        </div>
    </div>
    <script>
        let app = document.getElementById('app')
        let wrap = document.getElementById('wrap')
        let box = document.getElementById('box')
        app.addEventListener('click', () => {  
            console.log('app');
        }, true)
        wrap.addEventListener('click', () => {
            console.log('wrap');
        })
        box.addEventListener('click', () => {
            console.log('box');
        })
    </script>

image.png

我们点击图中黑色方块,也就是box容器,然后输出box, wrap, app,因为我们的addEventListener函数第三个并没有写参数,而它在没有参数的情况下默认是false,也就是说该事件会在冒泡阶段时处理。我们上面提到过,事件从目标元素开始向上冒泡,经过DOM树上的父元素,直到达到最外层的对象,而因为box被包裹在wrap里,wrap被包裹在app里,所以是先输出box,wrap,app.

而如果点击紫色元素,则输出wrap, app。点击蓝色,输出app。

app.addEventListener('click', () => {  
            console.log('app');
        }, true)
        wrap.addEventListener('click', () => {
            console.log('wrap');
        })
        box.addEventListener('click', () => {
            console.log('box');
        },true)

这里我们将appbox容器点击事件的第三个参数设置为true,也就是说两个容器的点击事件会在捕获阶段时处理。而wrap的点击事件在冒泡阶段处理。那么我们点击box(黑色容器),将输出app, box, wrap。当我们点击box容器时,先进行捕获阶段,事件从最外层元素开始,逐渐向内部元素传播,所以先打印app,再打印box。当事件到达目标元素时,再发生冒泡事件,从目标元素开始,逐渐向外部元素传播,所以最后打印wrap。

image.png

而如果我们将三个点击事件的第三个参数全部设置为true时,那么他们都将在捕获阶段进行处理,则打印app, wrap, box。

阻止事件传播

event.stopPropagation()

当我们调用此方法时,会阻止事件进行传播下去,但不会阻止其它事件处理程序被触发。我通过一个例子来给大家讲解:

app.addEventListener('click', (e) => {  // 绑定, 订阅, 注册
            console.log('app');
            e.stopPropagation()
        }, true)
        wrap.addEventListener('click', () => {
            console.log('wrap');
        }, )
        box.addEventListener('click', (e) => {
            console.log('box');
        }, true)

如上述代码,在正常情况下,点击box容器,如果不加这一行代码e.stopPropagation(), 那么appbox在捕获阶段触发,wrap在冒泡阶段触发。但如果在app容器的事件监听函数上面加了这一段代码,那么它会阻止事件进行传播。也就是说,当捕获阶段时,事件传播到了app的容器上,发现addEventListener函数的第三个参数为true,那么则触发该点击事件,但是发现这个点击事件的回调函数内有e.stopPropagation(),它会阻止事件继续传播下去,所以本该继续向内传播的事件被终止。所以这里只打印app

image.png

app.addEventListener('click', (e) => {  // 绑定, 订阅, 注册
            console.log('app');     
        }, true)
        wrap.addEventListener('click', () => {
            console.log('wrap');
        }, )
        box.addEventListener('click', (e) => {
            console.log('box');
            e.stopPropagation()
        })

来看看这段代码,点击box容器,那么在捕获阶段打印app,在冒泡阶段,当事件传播到box身上时,触发,打印box,但是监听器的回调函数里面存在e.stopPropagation(),所以它会阻止事件继续传播,最终打印 app, box。

调用该方法,不会阻止同一元素其它事件被触发

app.addEventListener('click', (e) => {  // 绑定, 订阅, 注册
            console.log('app');     
        }, true)
        wrap.addEventListener('click', () => {
            console.log('wrap');
        }, )
        box.addEventListener('click', (e) => {
            console.log('box');
            e.stopPropagation()
        })
        box.addEventListener('click', (e) => {
            console.log('box2');
        })

image.png

如图,打印app, box, box2。该方法不会阻止同一元素其他事件被触发。

event.stopImmediatePropagation()

调用该方法,它的效果跟event.stopPropagation()类似,但是会阻止同一元素的相同事件被触发

app.addEventListener('click', (e) => {  // 绑定, 订阅, 注册
            console.log('app');     
        }, true)
        wrap.addEventListener('click', () => {
            console.log('wrap');
        }, )
        box.addEventListener('click', (e) => {
            console.log('box');
            event.stopImmediatePropagation() 
        })
        box.addEventListener('click', (e) => {
            console.log('box2');
        })

若我们在box的监听上加上event.stopImmediatePropagation()函数,那么它会阻止box元素相同事件的触发,例如上述代码中box绑定的第二段点击事件。所以只会输出app, box

image.png

事件委托

事件委托(Event Delegation)是一种常用的 JavaScript 设计模式,用于处理事件监听和处理,特别是当需要为大量的子元素添加相同类型的事件监听器时。通过事件委托,我们可以将事件监听器绑定到父元素(通常是包含所有子元素的容器),而不是直接绑定到每一个子元素。

<ul id="ul">
        <li>a</li>
        <li>b</li>
        <li>c</li>
        <li>d</li>
        <li>e</li>
    </ul>

如果我们想为每一个列表项添加点击事件,传统的方法可能是遍历每一个列表项并分别绑定事件处理函数:

let lis = document.querySelectorAll('li')
    lisforEach((li) => {
        li.addEventListener('click', () => {
            console.log(li.innerText);
        })
    })
虽然这样添加点
击事件也是没问题的,但是这只是节点少的情况下,如果节点多的时候,那么就需要造成十分大的性能消耗。

使用事件委托,我们只需将事件监听器绑定到 ul 元素:

let ul = document.getElementById('ul')
    ul.addEveListener('click', (e) => {
        // console.log(e);
        console.log(e.target.innerText);
    })

当你点击列表项时,事件会冒泡到ul元素,然后通过检查e.target.innerText,来判断我们点击的是列表项中的哪一个元素。

不管li有多少个,我们最终只需要维护一个函数就够了!

总结一下

注意事项:

  1. 事件冒泡:事件委托依赖于事件冒泡机制,因此确保不阻止事件冒泡或在冒泡阶段处理事件。
  2. 目标检查:在事件处理函数中,通常需要检查 event.targetevent.currentTarget 来确定是哪一个元素触发了事件。
  3. 选择合适的父元素:选择一个恰当的父元素进行事件委托,通常是包含所有目标元素的最近的父容器。

事件委托是一种强大而灵活的技术,特别适用于处理大量的子元素事件。通过将事件监听器绑定到父元素,我们可以提高性能,简化代码,并支持动态添加的元素,从而更高效地管理和处理事件。

相关文章
|
1月前
|
JavaScript 前端开发
事件委托的性能优化是否适用于所有类型的应用程序?
【10月更文挑战第29天】事件委托的性能优化在具有大量相似DOM元素、动态生成DOM元素以及对性能要求高且交互频繁的应用中具有显著优势,但对于DOM结构简单、事件处理逻辑复杂且对兼容性要求高的老旧浏览器应用等类型的程序,其适用性则相对有限。在实际开发中,需要根据具体的应用程序特点和需求来综合判断是否采用事件委托的性能优化策略。
|
1月前
|
缓存 小程序 UED
小程序数据绑定机制的性能优化
【10月更文挑战第30天】
75 27
|
4月前
|
缓存 前端开发 网络协议
性能优化|几个方法让图片加载更快一些
对电商网页的性能而言,图片优化是至关重要的事情,本文就此探讨了一些简单、可靠的图片优化手段。
|
4月前
|
前端开发 JavaScript 开发者
掌握Web前端事件处理精髓:从事件冒泡到事件委托,轻松优化你的交互体验与代码性能!
【8月更文挑战第23天】在Web前端开发中,事件处理是实现用户交互的关键机制。其中,事件冒泡与事件委托是优化页面性能、简化代码的重要手段。事件冒泡是指事件从触发它的元素开始,沿着DOM树向上逐层传播至根节点的过程。通过阻止事件冒泡,可以控制事件的影响范围。而事件委托则是利用事件冒泡特性,在父元素上设置监听器来响应子元素的事件,这种方法减少了监听器的设置数量,特别适用于动态添加的子元素,提高了代码的可维护性和性能。掌握这两种技术,能帮助开发者构建更高效、更简洁的应用程序。
60 0
|
4月前
|
前端开发 JavaScript UED
现代前端开发中的动态组件加载与性能优化
传统的前端应用加载所有组件可能会导致性能问题和用户体验下降。本文讨论了现代前端开发中采用动态组件加载的策略,通过异步加载和按需渲染优化页面加载速度和资源利用效率。
|
7月前
|
缓存 JavaScript 前端开发
Axios 高阶技巧大揭秘:进阶用法与性能优化
Axios 高阶技巧大揭秘:进阶用法与性能优化
357 0
|
存储 移动开发 前端开发
前端经典面试题 | 性能优化之 懒加载
前端经典面试题 | 性能优化之 懒加载
|
前端开发
✨性能优化之防抖节流篇
✨性能优化之防抖节流篇
106 2
✨性能优化之防抖节流篇
|
Web App开发 JavaScript 前端开发
性能优化之通俗易懂学习requestAnimationFrame和使用场景举例
性能优化之通俗易懂学习requestAnimationFrame和使用场景举例
235 0
|
Web App开发 缓存 JavaScript
JavaScript 代码性能优化 - 从排查到处理
近期在对我们的控制台做性能优化,这次记录下代码执行方面的性能排查与优化(纯 JS 上的不包含 DOM 操作等优化)。其它的优化点以后有机会再分享。
JavaScript 代码性能优化 - 从排查到处理