10 分鐘上手 ECharts:從“能跑”到“生產級”的完整踩坑筆記
如果你也曾 復制了官方 Demo 卻不知道怎么拆、窗口一拉伸圖表就變形、切換標簽頁后內存暴漲——這篇博客就是為你寫的。
我會用 6 個遞進版本 的源碼,帶你把一張 最簡柱狀圖 逐步進化成 可銷毀、可重建、零泄漏 的響應式組件,順便把 ECharts 的核心 API 一次性講透。
這里先附上 echart官網
請先耐心閱讀文章,文章末附上的有完整源碼
00 前言:為什么又寫一篇 ECharts 入門?
ECharts 的官方例子足夠漂亮,但大多數教程只停在 “hello world” 級別:
echarts.init(dom).setOption(option);
然而真實業務里,我們至少要回答三個問題:
- 窗口拉伸怎么辦?
- 彈窗/標簽頁切換后圖表不見了,再打開為何一片空白?
- 反復進出頁面,內存為何節節攀升?
今天用 不到 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>
init
和 setOption
是 ECharts 的“開機鍵”和“遙控器”,一句話就能記住:
init 用于創建圖表實例,并指定渲染所需的 DOM 節點;
setOption 用于向該實例傳入配置項,以生成并更新圖表。
必須先執行 init 獲得實例,再調用 setOption,否則無法渲染。
此時打開瀏覽器,粉色區域出現柱狀圖——任務完成,但別急著提交代碼,因為拉伸窗口圖表不會跟著變。
02 讓圖表“長”在窗口上(Step-2)——響應式 101
ECharts 暴露的唯一武器是:resize()
我們只需在窗口尺寸變化時調用它。
關鍵細節:addEventListener
與 removeEventListener
必須指向同一個函數引用,否則解綁失敗 → 內存泄漏。
文件: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 在 beforeUnmount
或 useEffect 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(綁+解)×N
→ O(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
調用createChart
,onUnmounted
調用destroyChart
。 - React:在
useEffect(() => { createChart(); return destroyChart; }, []);
即可。 - 多圖表:把
myChart
換成數組或 Map,resize 監聽仍只需一次。
至此,內存泄漏、響應式、銷毀重建 三大痛點全部解決;
剩下的,就是去 ECharts 官方示例 里復制更炫的 option
了!
Happy charting! 🎉
如果有任何疑問,歡迎在評論區留言討論!