全部
常见问题
产品动态
精选推荐

使用事件代理提升 Web 应用性能

管理 管理 编辑 删除

前言

在构建现代 Web 应用时,我们经常需要处理大量数据,并将其渲染为列表或表格,对每个数据项绑定一个事件处理程序是常见的做法。但随着数据量的增长,这种做法将会导致严重的性能问题;本文旨在介绍事件代理这一解决方案,帮助开发者优化 Web 应用性能,提升用户体验。

常见场景

在构建现代 Web 应用时,我们会使用各种现代 Web 框架来简化我们的工作。以 Vue 为例,渲染一个列表的代码大致如下:

<template>
    <ul>
        <li v-for="item of someList" :key="item.id" @click="handleItemClick">{{ item.name }}</li>
    </ul>
</template>

<script setup lang="ts">
const someList = [
    ...
];

const handleItemClick = (e: Event) => {
    ...
}

</script>   

someList 中存在多少条数据,页面中便会存在多少个事件监听处理程序。当 someList 数据量巨大时,大量的事件监听器会占用大量内存,并导致严重的性能问题。此时我们可以通过事件代理,将事件处理代理给父元素。事件代理利用了事件冒泡的机制,使得只需在父元素上绑定一个事件监听器,即可处理所有子元素的事件,从而大大减少事件监听器的数量,代码如下:

<template>
    <ul @click="handleListClick">
        <li v-for="item of someList" :key="item.id" :data-id="item.id">{{ item.name }}</li>
    </ul>
</template>

<script setup lang="ts">
const someList = [
    ...
];

const handleListClick= (e: Event) => {
    const { id } = e.target.dataset;
    if (id !== undefined) {
        ...
    }
}

</script>   


通过将子元素需要的数据绑定在 dataset 属性中,我们可以方便的在事件代理中获取这些数据,解决了参数传递的问题。然而实际开发中,列表项远比示例中要复杂得多,可能会包含多个图标、按钮或其他子元素。

此时我们就需要一种方法来区分不同的按钮;同样我们可以使用 dataset 属性,为不同的按钮设置不同的 dataset 属性值,然后在事件处理程序中通过 event.target.dataset 来判断哪个按钮触发了事件,代码如下;

b7d52202501221038236709.png

<template>
    <ul @click="handleListClick">
        <li v-for="item of someList" :key="item.id">
            <p>文案</p>
            <button :data-id="item.id" data-event="event1">button1</button>
            <button :data-id="item.id" data-event="event2">button2</button>
        </li>
    </ul>
</template>

<script setup lang="ts">

const someList = [
    ...
];

const handleListClick= (e: Event) => {
    const { id, event } = e.target.dataset;
    if (id === undefined || event === undefined) return;
    if (event === "event1") {
        ...
    } else if (event === "event2") {
        ...
    }
}

</script>   


有时,列表子项内包含多个无需任何事件处理程序的子元素,而数据绑定在列表子项的 dataset 属性上。此时,如果点击子元素, e.target 指向的是子元素,而非列表元素,无法直接获取 dataset。 e.target.closest(selector) 方法可以向上查找匹配选择器的最近祖先元素,从而解决这个问题,代码如下:

<template>
    <ul @click="handleListClick">
        <li v-for="item of someList" :key="item.id" :data-uid="item.uid">
            <p>文案</p>
            <img src="../avatar.png" />
        </li>
    </ul>
</template>

<script setup lang="ts">

const someList = [
    ...
];

const handleListClick= (e: Event) => {
    let { uid } = e.target.dataset;
    if (uid === undefined) {
        const listItem = e.target.closest("li")
        if (!listItem) return;
        uid = listItem.dataset.uid;
    }
    
    ....
}

</script>  

结语

需要注意的是,事件代理并非适用于所有场景,开发者需要针对具体情况进行实际权衡。在需要处理大量相似元素的事件时,事件代理无疑是一种优秀的解决方案。本文的代码示例中以 Vue 框架为示例,但核心思想应用在 React 或其他框架上也同样使用,希望本文能够帮助开发者更好的使用事件代理,在实际开发中做出更稳妥的选择。

请登录后查看

plz 最后编辑于2025-01-22 11:11:24

快捷回复
回复
回复
回复({{post_count}}) {{!is_user ? '我的回复' :'全部回复'}}
排序 默认正序 回复倒序 点赞倒序

{{item.user_info.nickname ? item.user_info.nickname : item.user_name}} LV.{{ item.user_info.bbs_level }}

作者 管理员 企业

{{item.floor}}# 同步到gitee 已同步到gitee {{item.is_suggest == 1? '取消推荐': '推荐'}}
{{item.is_suggest == 1? '取消推荐': '推荐'}}
沙发 板凳 地板 {{item.floor}}#
{{item.user_info.title || '暂无简介'}}
附件

{{itemf.name}}

{{item.created_at}}  {{item.ip_address}}
{{item.like_count}}
{{item.showReply ? '取消回复' : '回复'}}
删除
回复
回复

{{itemc.user_info.nickname}}

{{itemc.user_name}}

回复 {{itemc.comment_user_info.nickname}}

附件

{{itemf.name}}

{{itemc.created_at}}
{{itemc.like_count}}
{{itemc.showReply ? '取消回复' : '回复'}}
删除
回复
回复
查看更多
46
{{like_count}}
{{collect_count}}
添加回复 ({{post_count}})

相关推荐

快速安全登录

使用微信扫码登录
{{item.label}} 加精
{{item.label}} {{item.label}} 板块推荐 常见问题 产品动态 精选推荐 首页头条 首页动态 首页推荐
取 消 确 定
回复
回复
问题:
问题自动获取的帖子内容,不准确时需要手动修改. [获取答案]
答案:
提交
bug 需求 取 消 确 定

微信登录/注册

切换手机号登录

{{ bind_phone ? '绑定手机' : '手机登录'}}

{{codeText}}
切换微信登录/注册
暂不绑定
CRMEB客服

CRMEB咨询热线 咨询热线

400-8888-794

微信扫码咨询

CRMEB开源商城下载 源码下载 CRMEB帮助文档 帮助文档
返回顶部 返回顶部
CRMEB客服