10 分钟上手 ECharts:从“能跑”到“生产级”的完整踩坑之旅

10 分钟上手 ECharts:从“能跑”到“生产级”的完整踩坑笔记

如果你也曾 复制了官方 Demo 却不知道怎么拆窗口一拉伸图表就变形切换标签页后内存暴涨——这篇博客就是为你写的。
我会用 6 个递进版本 的源码,带你把一张 最简柱状图 逐步进化成 可销毁、可重建、零泄漏 的响应式组件,顺便把 ECharts 的核心 API 一次性讲透。


这里先附上 echart官网

请先耐心阅读文章,文章末附上的有完整源码

00 前言:为什么又写一篇 ECharts 入门?

ECharts 的官方例子足够漂亮,但大多数教程只停在 “hello world” 级别:

echarts.init(dom).setOption(option);

然而真实业务里,我们至少要回答三个问题:

  1. 窗口拉伸怎么办?
  2. 弹窗/标签页切换后图表不见了,再打开为何一片空白?
  3. 反复进出页面,内存为何节节攀升?

今天用 不到 120 行代码 把这三个坑填平,让你 copy-paste 即可投产


01 最小可运行版本(Step-1)——先跑起来再说

文件:step1-hello.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step1</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><script>// 1. 描述你要画什么const option = {title: { text: 'First ECharts' },tooltip: {},legend: { data: ['销量'] },xAxis: { data: ['衬衫','羊毛衫','裤子','袜子','高跟鞋'] },yAxis: {},series: [{ name: '销量', type: 'bar', data: [5,20,36,10,10] }]};// 2.  init → setOption 两行经典 APIconst myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);</script>
</body>
</html>

initsetOption 是 ECharts 的“开机键”和“遥控器”,一句话就能记住:

init 用于创建图表实例,并指定渲染所需的 DOM 节点;
setOption 用于向该实例传入配置项,以生成并更新图表。
必须先执行 init 获得实例,再调用 setOption,否则无法渲染。

此时打开浏览器,粉色区域出现柱状图——任务完成,但别急着提交代码,因为拉伸窗口图表不会跟着变


02 让图表“长”在窗口上(Step-2)——响应式 101

ECharts 暴露的唯一武器是:resize()
我们只需在窗口尺寸变化时调用它。

关键细节addEventListenerremoveEventListener 必须指向同一个函数引用,否则解绑失败 → 内存泄漏。

文件:step2-resize.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step2</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><script>const option = {title: { text: 'First ECharts' },tooltip: {},legend: { data: ['销量'] },xAxis: { data: ['衬衫','羊毛衫','裤子','袜子','高跟鞋'] },yAxis: {},series: [{ name: '销量', type: 'bar', data: [5,20,36,10,10] }]};const myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);/* 统一句柄:后面销毁时还要用 */const handleResize = () => myChart && myChart.resize();window.addEventListener('resize', handleResize);</script>
</body>
</html>

为什么这个匿名函数要这样写?
const handleResize = () => myChart && myChart.resize();

JavaScript 的 && 运算符具备短路特性:左侧表达式为真时,才继续执行右侧;左侧为假时,整个表达式立即返回假,右侧代码不会被执行

resize 场景下,左侧的 myChart 若因销毁而变为 null,右侧的 resize() 调用就会被自动跳过,从而避免空指针错误,实现**一行代码完成“存在判断 + 方法调用”**的防御式逻辑。

现在拉伸窗口,柱子实时重排,响应式闭环达成


03 弹窗关闭 ≠ 直接 remove DOM(Step-3)——销毁实例

场景

  • 标签页切换、弹窗关闭、路由跳转 → 容器节点被移除。
  • 用户再次打开弹窗,发现图表区域空白,控制台报 Cannot read properties of null

原因
dispose() 没调用,ECharts 实例还在旧 DOM 碎片里,内存没释放节点已不存在

官方原话

“在容器节点被销毁时,总是应调用 echartsInstance.dispose 以销毁实例释放资源,避免内存泄漏。”

文件:step3-dispose.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step3</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><button id="ctrl">销毁</button><script>const option = { ... };   // 同上const myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);const handleResize = () => myChart && myChart.resize();window.addEventListener('resize', handleResize);/* 释放内存的逻辑 */const destroyChart = () => {if (myChart) {myChart.dispose();   // 释放 WebGL/Canvas 资源myChart = null;      // 告诉垃圾回收器“我清空了”window.removeEventListener('resize', handleResize);}};document.getElementById('ctrl').addEventListener('click', destroyChart);</script>
</body>
</html>

最佳实践
谁先删 DOM,谁负责 dispose;Vue/React 在 beforeUnmountuseEffect cleanup 里统一销毁。


04 一键“销毁/重建”开关(Step-4)——完整切换逻辑

把销毁/创建封装成两个纯函数,再用按钮模拟“标签页切换”:

文件:step4-toggle.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step4</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><button id="ctrl">销毁</button><script>const option = { ... };   // 同上let myChart = null;let exist = true;                       // 当前是否存在const btn = document.getElementById('ctrl');const createChart = () => {myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);window.addEventListener('resize', handleResize);};const destroyChart = () => {if (myChart) {myChart.dispose();myChart = null;window.removeEventListener('resize', handleResize);}};btn.addEventListener('click', () => {exist ? destroyChart() : createChart();exist = !exist;btn.innerText = exist ? '销毁' : '创建';});createChart();   // 首次自动创建</script>
</body>
</html>
  • 第一次点击 → 销毁(按钮文字变“创建”)
  • 第二次点击 → 重建(按钮文字变“销毁”)

内存监控:Chrome DevTools → Memory → Heap snapshot,反复切换,节点数不再上涨


05 算法级优化(Step-Final)——终身只绑一次 resize

大体写完了,但细节和性能上我们还可以进行优化,比如通过引入三元运算符或者封装函数等方式来优化性能

问题
每次重建都 addEventListener → 理论上会重复绑定同一类型事件(虽然浏览器会去重,但仍不优雅)。

思路
resize 监听与图表生命周期脱钩,只要全局存在一次即可;内部用“懒调度”判断实例是否存在。

文件:step-final.html

<!DOCTYPE html>
<html>
<head><meta charset="utf-8" /><title>ECharts Step-Final</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script><style>#main{height:60vh;background:pink;}</style>
</head>
<body><div id="main"></div><button id="ctrl">销毁</button><script>const option = { ... };   // 同上let myChart = null;let exist = true;const btn = document.getElementById('ctrl');/* 终身只绑一次 resize,无论有多少图表 */window.addEventListener('resize', () => myChart && myChart.resize());const createChart = () => {myChart = echarts.init(document.getElementById('main'));myChart.setOption(option);};const destroyChart = () => {myChart && myChart.dispose();myChart = null;};btn.addEventListener('click', () => {exist ? destroyChart() : createChart(); // 表格存在就执行销毁,不存在就执行创建exist = !exist;btn.innerText = exist ? '销毁' : '创建';});createChart();   // 首次自动创建</script>
</body>
</html>

复杂度从 O(绑+解)×NO(1)多图表、路由切换、弹窗堆叠场景下同样适用。


06 直接投产:最终 120 行模板(up主写的完整原生前端三剑客代码)

<!DOCTYPE html>
<html><head><meta charset="utf-8" /><title>echart practice</title><script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script></head><style>#main,html,body {width: 100%;}#main {height: 400px;}
</style><body><!-- 准备的一个定义好宽高背景色的DOM容器 --><div id="main" style="background-color: pink;"></div><button id="ctrl">销毁</button><!-- 为echart初始化实例 --><script type="text/javascript">// type后面那一坨都是老版html需要写的,用于指定脚本语言,现在HTML5可以省略了// TODO.1 相关变量配置// 表格let myChart = null// 配置项let option = {// 标题组件title: {text: 'First Echart Practice', // 主标题文本subtext: '副标题', // 副标题// left & right & center 用来控制水平位置// top 用于控制垂直位置},  // 提示框组件()鼠标悬停时弹出tooltip: {trigger: 'item',            // 触发方式:'item'(单点) | 'axis'(坐标轴) | 'none'// formatter: '{b}<br/>{a}: {c}' // 自定义浮层内容,模板或回调函数},// 图例组件(点击可控制系列显隐)legend: {data: ['销量'],             // 必须与 series[i].name 保持一致,才能对应// orient: 'horizontal',     // 排列方向:'horizontal'|'vertical'// left: 'right',            // 位置,同 title},// X 轴xAxis: {data: ['衬衫', '羊毛衫', '裤子', '袜子', '高跟鞋']},// Y 轴yAxis: {},// 系列列表(真正决定“画什么图”)series: [{name: '销量',             // 与 legend.data 对应,悬停提示也会用 type: 'bar', data: [5, 20, 36, 10, 10,20]}],}// TODO.2 监听页面大小变化事件const handleResize = () => myChart && myChart.resize() // 防御式写法,如果myChart已被销毁就短路返回,不会执行 resize();如果myChart存在,正常调resize()让图表随窗口大小重绘。// TODO.3 表格创建初始化函数const createChart = () => {myChart = echarts.init(document.getElementById('main'))console.log('表格对象实例化完成')myChart.setOption(option)console.log('表格对象展示完成')window.addEventListener('resize', handleResize) // 浏览器原生事件,当窗口(window)大小发生变化 时触发console.log('表格已建立')}// TODO.4 销毁实例const destroyChart = () => {myChart.dispose();   // 释放内存myChart = null;      // 垃圾回收,将变量制空window.removeEventListener('resize', handleResize);console.log('图表已销毁');}createChart()// TODO.5 销毁和创建实例let ctrlFactor = true // 为true时表格存在const btn = document.getElementById('ctrl')console.log('初始化创建成功')btn.addEventListener('click', () => {btn.innerText = ctrlFactor ? '创建' : '销毁' // 通过控制因子判断按钮文字内容ctrlFactor ? destroyChart() : createChart() // 通过控制因子去判断表格操作ctrlFactor = !ctrlFactor})</script></body>
</html>

复制→保存→打开浏览器,你就拥有了一个:

  • 响应式
  • 可销毁/重建
  • 零内存泄漏

的 ECharts 基准模板,后续只需替换 option 即可快速出图!


07 结语:把模板塞进你的脚手架

  • Vue:在 onMounted 调用 createChartonUnmounted 调用 destroyChart
  • React:在 useEffect(() => { createChart(); return destroyChart; }, []); 即可。
  • 多图表:把 myChart 换成数组或 Map,resize 监听仍只需一次。

至此,内存泄漏、响应式、销毁重建 三大痛点全部解决;
剩下的,就是去 ECharts 官方示例 里复制更炫的 option 了!

Happy charting! 🎉


如果有任何疑问,欢迎在评论区留言讨论!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921993.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

二级缓存在实际项目中的应用

二级缓存在项目中的应用 目录 1. 二级缓存简介2. 应用场景3. 重难点分析4. 结合SpringBoot使用5. 最佳实践与案例6. 总结 1. 二级缓存简介 1.1 什么是二级缓存 二级缓存&#xff08;Second-Level Cache&#xff09; 是Hibernate框架中的一个重要特性&#xff0c;它提供了应…

深入浅出CRC校验:从数学原理到单周期硬件实现 (2)CRC数学多项式基础

数学的优雅&#xff1a;剖开CRC的多项式除法核心看似复杂的CRC校验&#xff0c;其核心建立在优雅的数学基础之上。本文将为您揭开CRC算法的数学面纱&#xff0c;让您真正理解多项式除法的精妙之处。模2运算&#xff1a;CRC世界的特殊算术 CRC计算建立在一种特殊的代数系统上——…

软考初级有没有必要考?

对正在学习相关专业的学生或者是行业新人&#xff0c;这篇文章从软考初级的含义、适合哪些人考、考试难度等方面解答&#xff0c;帮助你判断要不要报考。一、软考初级是什么&#xff1f; 软考初级是软考体系里面的基础级别&#xff0c;主要面向在校大学生或是IT行业新人&#x…

11 Prompt 工程进阶:Few-shot 与 Chain-of-Thought

11 Prompt 工程进阶&#xff1a;Few-shot 与 Chain-of-Thought 前10节总结 & 后10节展望 在前 10 节&#xff0c;我们已经完成了 AI 产品经理的入门阶段&#xff1a; 1–3&#xff1a;理解了大模型的基本概念、Token、Prompt 基础&#xff1b;4–5&#xff1a;体验了本地部…

ARM1.(ARM体系结构)

1.基本概念嵌入式:以应用为心&#xff0c;以计算机技术为础&#xff0c;软便件可被的专用计算机系统。计算机系统的软件基本组成: 系统软件、应用软件。计算机系统的硬件基本组成&#xff1a;运算器、控制器、存诸器、输入设备、输出设备日常生活中遇到的专业术语&#xff1a…

Django全栈班v1.01 Python简介与特点 20250910

从零开始的Python编程之旅 “人生苦短&#xff0c;我用Python。”这不仅仅是Python程序员的口头禅&#xff0c;更是对Python强大能力的最好诠释&#xff01;&#xff01;&#xff01; 为什么全世界有超过1500万开发者选择Python&#xff1f; 为什么Python连续多年蝉联最受欢…

【WebApi】什么情况开启如何开启缓存

在 ASP.NET Core WebAPI 中开启缓存是优化性能、减少服务器负载和提升用户体验的非常重要的手段。但并非所有情况都适合开启缓存。 下面我将从 “什么情况下开启” 和 “如何开启” 两个方面为你详细解释。 一、什么情况下应该开启缓存? 总的来说,缓存适用于 “变化不频繁但…

Go语言类型断言全解析

类型断言的基本概念类型断言(Type Assertion)是Go语言中用于检查接口值底层具体类型的机制。它本质上是一种运行时类型检查的操作&#xff0c;允许程序在运行时判断接口变量是否持有特定的类型值&#xff0c;并提取该类型的值。这是Go语言类型系统中的一个重要特性&#xff0c;…

大模型在题目生成中的安全研究:攻击方法与防御机制

大模型在题目生成中的安全研究&#xff1a;攻击方法与防御机制 文章目录大模型在题目生成中的安全研究&#xff1a;攻击方法与防御机制一、引言二、大模型在题目生成中的安全漏洞与攻击方法2.1 大模型在题目生成中的安全漏洞分析2.1.1 训练数据相关漏洞2.1.2 模型架构与特性相关…

跟做springboot尚品甄选项目(二)

登录功能的书写 后端接口的书写 &#xff08;1&#xff09;创建配置文件 粘贴这两个文件&#xff08;E:\project\AllProJect\Shangpin Selection\项目材料素材\资料\资料\03-配置文件&#xff09; 在spzx-manager服务的src/resources目录下创建application.yml、application-…

前后端接口调试提效:Postman + Mock Server 的工作流

前后端接口调试提效&#xff1a;Postman Mock Server 的工作流 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是…

大带宽香港云服务器在数据传输速度上有何优势?

为方便站长快速部署网站、优化用户访问体验&#xff0c;当下众多实力强劲的香港数据中心&#xff0c;均推出了大带宽云服务器产品。不过&#xff0c;市面上不少数据中心虽宣称提供 “专属大带宽”&#xff0c;但其线路配置中&#xff0c;国际线路占比高、绕行链路多&#xff0c…

HT862 智能音频功率放大器:为便携音频设备打造高效稳定的音质解决方案

在蓝牙音箱、智能手机、便携式游戏机等设备的设计中&#xff0c;音频功率放大器是决定音质表现、续航能力与使用稳定性的关键部件。一款优质的音频功放&#xff0c;不仅需要输出足够的功率以满足清晰响亮的听觉需求&#xff0c;还需在能效、温控、适配性上达到平衡&#xff0c;…

HarmonyOS-ArkUI Web控件基础铺垫7-HTTP SSL认证图解 及 Charles抓包原理 及您为什么配置对了也抓不到数据

HarmonyOS-ArkUI Web控件基础铺垫6--TCP协议- 流量控制算法与拥塞控制算法 HarmonyOS-ArkUI Web控件基础铺垫5--TCP协议- 动画展示超时重传&#xff0c;滑动窗口&#xff0c;快速重传 HarmonyOS-ArkUI Web控件基础铺垫4--TCP协议- 断联-四次挥手解析 HarmonyOS-ArkUI Web控件…

【qt】通过TCP传输json,json里包含图像

主要是使用协议头 发送方connect(m_pDetectWorker, &DetectionWorker::sig_detectImg, this, [](const QJsonObject &json){// 转换为JSON数据QJsonDocument doc(json);QByteArray jsonData doc.toJson(QJsonDocument::Compact);// 构建增强协议头struct EnhancedHead…

四,基础开发工具(下)

4.5自动构建make/Makefile4.5.1基本使用1示例2进一步解释3实践4最佳实践4.6练习&#xff1a;进度条4.6.1倒计时4.6.2进度条version14.6.2进度条version24.7版本控制器Git4.7.1git操作1操作一次&#xff0c;以后不愁2经典"三件套"3常用4版本回退4.7.2小结4.5自动构建m…

C++基本数据类型的范围

文章目录不同位数的系统下各个类型所占字节数如何存储的我发现我能搜到的相关文章都只讲了这些数据类型的范围是这样的&#xff0c;不说实际的存储情况&#xff0c;当你了解了类型实际是如何存储的&#xff0c;再去记忆这些范围就简单了&#xff0c;所以就有了这篇文章不同位数…

基于社交媒体数据的公众情绪指数构建与重大事件影响分析

一、引言在信息爆炸的时代&#xff0c;社交媒体&#xff08;如微博、Twitter&#xff09;已成为公众表达情绪、讨论热点事件的主要平台。通过分析社交媒体数据&#xff0c;可以构建公众情绪指数&#xff0c;并进一步研究其与股市波动、政策发布等重大事件的关联性。本文将介绍如…

OpenLayers数据源集成 -- 章节七:高德地图集成详解

前言在前面的文章中&#xff0c;我们学习了OpenLayers的瓦片调试&#xff08;VectorTileDebug&#xff09;技术。本文将深入探讨OpenLayers中高德地图的集成方法&#xff0c;这是WebGIS开发中接入商业地图服务的重要技术。高德地图作为国内领先的地图服务提供商&#xff0c;提供…

海外代理IP平台Top3评测:LoongProxy、神龙动态IP、IPIPGO哪家更适合你?

在当今互联网环境中&#xff0c;代理IP服务已成为许多企业和个人用户的刚需。无论是数据采集、市场调研还是账号管理&#xff0c;优质的代理IP都能大幅提升工作效率。本文将针对LoongProxy、神龙海外动态IP和IPIPGO这三家主流代理IP服务商进行横向评测&#xff0c;帮助你根据自…