网站首页 > 技术文章 正文
前端开发者们注意啦,今天给大家带来一个关于Element-plus组件的内存泄漏问题,经过测试,以下组件存在内存泄漏的现象:
- el-carousel
- el-select + el-options
- el-descriptions
- el-tag
- el-dialog
- el-notification
- el-loading
- el-result
- el-message
- el-button
- el-tabs
- el-menu
- el-popper
以下是我在 Vue 3.5.13 和 Element Plus 2.9.7 版本下的排查环境:
- Vue版本:3.5.13
- Element Plus版本:2.9.7
- 操作系统:Windows 10
- 浏览器:Edge 134.0.3124.85 (正式版)
- 构建工具:Webpack
排查过程
一开始,我也没有怀疑这种高 Star 的开源组件,觉得肯定是我写的代码有问题。为了追查到底,我主要用了 ElDialog 组件,封装成了一个命令式的 Dialog组件,避免频繁使用 v-model参数。然后,我就把怀疑的目光投向了 ElDialog。
结果,经过测试,果然,Dialog组件在关闭和销毁时,会导致内存使用飙升。特别是当 Dialog 中包含各种表单组件时,一旦打开,就会创建大量的 Element 元素,导致内存泄漏。为了解决这个问题,我用了 FinalizationRegistry类追踪 Dialog 组件实例的销毁,代码如下:
const finalizerRegistry = new FinalizationRegistry((heldValue) => {
  console.log('Finalizing instance: ',heldValue);
});
// 在创建处监听
const heldValue = Symbol(`DialogCommandComponent_${Date.now()}`);
finalizerRegistry.register(this, heldValue);
console.log(`Constructed instance:`,heldValue);通过测试,发现Dialog组件的销毁过程没有产生销毁信息,这意味着它没有正确地释放资源。于是,我决定进一步分析,是否是Dialog组件内部的引用问题导致元素未能销毁。我尝试用纯el-dialog组件进行测试,结果同样发现内存泄漏。
扩展测试
为了进一步验证其他组件是否也存在类似问题,我编写了一段代码,检查了多个 Element-plus 组件的内存占用情况。以下是相关代码:
<template>
  <div>
    <el-button @click="fn2">Reset</el-button>
</div>
<el-dialog v-model="model" destroy-on-close @closed="fn1" append-to-body v-if="destroyDialogModelValue"></el-dialog>
<el-button @click="fn0" v-if="!button" primse>Click</el-button>
<div class="weak" v-if="!button">xxx</div>
<el-input v-if="!button" />
<el-border v-if="!button" />
<el-select v-if="!button">
    <el-option>1111</el-option>
</el-select>
<el-switch v-if="!button" />
<el-radio v-if="!button" />
<el-rate v-if="!button" />
<el-slider v-if="!button" />
<el-time-picker v-if="!button" />
<el-time-select v-if="!button" />
<el-transfer v-if="!button" />
<el-tree-select v-if="!button" />
<el-calendar v-if="!button" />
<el-card v-if="!button" />
<el-carousel height="150px" v-if="!button">
    <el-carousel-item v-for="item in 4" :key="item">
      <h3 class="small justify-center" text="2xl">{{ item }}</h3>
    </el-carousel-item>
</el-carousel>
<el-descriptions title="User Info" v-if="!button">
    <el-descriptions-item label="Username">kooriookami</el-descriptions-item>
</el-descriptions>
<el-table style="width: 100%" v-if="!button">
    <el-table-column prop="date" label="Date" width="180" />
    <el-table-column prop="name" label="Name" width="180" />
    <el-table-column prop="address" label="Address" />
</el-table>
<el-avatar v-if="!button" />
<el-pagination layout="prev, pager, next" :total="50" v-if="!button" />
<el-progress :percentage="50" v-if="!button" />
<el-result icon="success" title="Success Tip" sub-title="Please follow the instructions" v-if="!button">
    <template #extra>
      <el-button type="primary">Back</el-button>
    </template>
</el-result>
<el-skeleton v-if="!button" />
<el-tag v-if="!button" />
<el-timeline v-if="!button" />
<el-tree v-if="!button" />
<el-avatar v-if="!button" />
<el-segmented size="large" v-if="!button" />
<el-dropdown v-if="!button">
    <span class="el-dropdown-link">
      Dropdown List
      <el-icon class="el-icon--right">
        <arrow-down />
      </el-icon>
    </span>
    <template #dropdown>
      <el-dropdown-menu>
        <el-dropdown-item>Action 1</el-dropdown-item>
        <el-dropdown-item>Action 2</el-dropdown-item>
        <el-dropdown-item>Action 3</el-dropdown-item>
        <el-dropdown-item disabled>Action 4</el-dropdown-item>
        <el-dropdown-item divided>Action 5</el-dropdown-item>
      </el-dropdown-menu>
    </template>
</el-dropdown>
<el-menu class="el-menu-demo" mode="horizontal" v-if="!button">
    <el-menu-item index="1">Processing Center</el-menu-item>
    <el-sub-menu index="2">
      <template #title>Workspace</template>
      <el-menu-item index="2-1">item one</el-menu-item>
      <el-menu-item index="2-2">item two</el-menu-item>
      <el-menu-item index="2-3">item three</el-menu-item>
      <el-sub-menu index="2-4">
        <template #title>item four</template>
        <el-menu-item index="2-4-1">item one</el-menu-item>
        <el-menu-item index="2-4-2">item two</el-menu-item>
        <el-menu-item index="2-4-3">item three</el-menu-item>
      </el-sub-menu>
    </el-sub-menu>
    <el-menu-item index="3" disabled>Info</el-menu-item>
    <el-menu-item index="4">Orders</el-menu-item>
</el-menu>
<el-steps style="max-width: 600px" active="0" finish-status="success" v-if="!button">
    <el-step title="Step 1" />
    <el-step title="Step 2" />
    <el-step title="Step 3" />
</el-steps>
<el-tabs class="demo-tabs" v-if="!button">
    <el-tab-pane label="User" name="first">User</el-tab-pane>
    <el-tab-pane label="Config" name="second">Config</el-tab-pane>
    <el-tab-pane label="Role" name="third">Role</el-tab-pane>
    <el-tab-pane label="Task" name="fourth">Task</el-tab-pane>
</el-tabs>
<el-alert title="Success alert" type="success" v-if="!button" />
<el-drawer title="I am the title" v-if="!button">
    <span>Hi, there!</span>
</el-drawer>
<div v-loading="model" v-if="!button"></div>
<el-popconfirm confirm-button-text="Yes" cancel-button-text="No" icon-color="#626AEF"
    title="Are you sure to delete this?" v-if="!button">
    <template #reference>
      <el-button>Delete</el-button>
    </template>
</el-popconfirm>
<el-popover class="box-item" title="Title" content="Top Center prompts info" placement="top" v-if="!button">
    <template #reference>
      <div>top</div>
    </template>
</el-popover>
<el-tooltip class="box-item" effect="dark" content="Top Left prompts info" placement="top-start" v-if="!button">
    <div>top-start</div>
</el-tooltip>
</template>
<script setup>
import { ref } from"vue";
import { ElMessage, ElMessageBox, ElNotification } from"element-plus";
const model = ref(false);
const destroyDialogModelValue = ref(false);
const button = ref(false);
function fn0() {
  model.value = true;
  destroyDialogModelValue.value = true;
  ElMessage("This is a message.");
  ElMessageBox.alert("This is a message", "Title");
  ElNotification({
    title: "Title",
    message: "This is a reminder",
  });
}
function fn1() {
console.log("closed");
  destroyDialogModelValue.value = false;
  button.value = true;
}
function reset() {
  model.value = false
}
</script>
<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>每次点击“Click”按钮后,我关闭所有弹窗,再点击“Reset”按钮,然后重复上述操作,发现内存占用一直在上涨。经过反复操作,最终确定了Element-plus的多个组件存在内存泄漏问题。
未能解决
面对这个问题,我也查找了大量的资源,包括 Element-plus的GitHub issues和相关论坛,但现有的解决方法基本无效。经过思考,以下是我想到的几种可能的解决方案:
- 是否可以为有泄漏的组件手动实现销毁机制?
- ElDialog是否只在全局使用一两个实例?
- 是否能将所有路由都打成单页面应用(SPA)?
- 是否需要修改源码?
目前,这些方案都没有给我带来太大突破,但仍在继续研究中。
猜你喜欢
- 2025-09-13 从 Java 代码逆向工程生成 UML 类图和序列图
- 2025-09-13 Lovart深度体验:不止是出图,它真能帮你干活!
- 2025-09-13 5款最佳的macOS菜单栏应用,你知道几个?
- 2025-09-13 谷歌正式发布Android 12,UI更好看,打造属于自己的定制化属性
- 2025-09-13 MFC IP地址控件、拆分按钮和超链接
- 2025-09-13 键盘上的Enter键为什么翻译成“回车”?
- 2025-09-13 秒搭大厂级后台!Arco Design 组件库,让设计稿和开发无缝对接
- 2025-09-13 CSS锚点定位:前端布局的革命性突破
- 2025-09-13 前端佬们!塌房了!用过Element-Plus的进来~
- 2025-09-13 被偷家了!崩溃!!Element-plus组件测试的后续~
- 最近发表
- 
- 聊一下 gRPC 的 C++ 异步编程_grpc 异步流模式
- [原创首发]安全日志管理中心实战(3)——开源NIDS之suricata部署
- 超详细手把手搭建在ubuntu系统的FFmpeg环境
- Nginx运维之路(Docker多段构建新版本并增加第三方模
- 92.1K小星星,一款开源免费的远程桌面,让你告别付费远程控制!
- Go 人脸识别教程_piwigo人脸识别
- 安卓手机安装Termux——搭建移动服务器
- ubuntu 安装开发环境(c/c++ 15)_ubuntu安装c++编译器
- Rust开发环境搭建指南:从安装到镜像配置的零坑实践
- Windows系统安装VirtualBox构造本地Linux开发环境
 
- 标签列表
- 
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (77)
- vector线程安全吗 (73)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)
 
