Chart.js渲染月度汇率图表出现顺序颠倒与Canvas占用错误
问题成因
1. 核心根源:变量作用域问题
你使用var声明的i、chr、days都属于函数级作用域变量,不存在块级作用域绑定。for循环执行完毕后,这些变量的值已经是最后一次循环的结果:i=1、chr='b'、days为10月的日期数组。
AJAX属于异步请求,等success回调触发时,循环早已执行完成,所有回调拿到的chr都是最终的'b',两次渲染都会尝试往id="chartb"的Canvas节点写入内容:
- 先返回的请求会先占用
chartb节点,后返回的请求再次操作同一个Canvas时,就会抛出Canvas is already in use的错误 - 本该渲染到
charta节点的9月数据没有写入对应位置,自然会出现图表顺序错乱的问题
2. 次要影响因素:异步请求返回顺序不固定
就算解决了作用域问题,异步请求的返回顺序不受调用顺序控制,10月的请求可能比9月的请求更早响应,也可能出现10月图表先渲染的问题。
解决方案
方案1:快速修复作用域问题
将var声明的循环变量改为let,let支持块级作用域,每次循环都会绑定独立的变量值,回调里拿到的就是当前循环对应的chr、days值。同时新增Chart实例销毁逻辑,避免潜在的复用冲突。
// 将var改为let启用块级作用域 for (let i = 0; i < interval.length; i++) { let days = []; let day = new Date(interval[i][0].getTime()); for (; day <= interval[i][1]; day.setDate(day.getDate() + 1)){ days.push(new Date(day).getDate()); } let chr = String.fromCharCode(97 + i); $('#charts').append(`<div style="width: 800px; height: 400px;"><canvas id="chart${chr}"></canvas></div>`); $.ajax({ type: 'GET', url: "https://localhost:5001/currency/" + selectedCurrency, dataType: 'json', data: { StartDate: interval[i][0].toISOString().split('T')[0], EndDate: interval[i][1].toISOString().split('T')[0] }, success: function(data) { const ctx = document.getElementById(`chart${chr}`).getContext('2d'); // 渲染前先销毁已存在的Chart实例,避免报错 if(Chart.getChart(ctx)) { Chart.getChart(ctx).destroy(); } new Chart(ctx, { type: 'line', data: { labels: days, datasets: [{ label: label, borderColor: '#006241', borderWidth: 1, fill: true, data: data }] }, }); } }); }
方案2:严格保证图表渲染顺序
如果对图表顺序要求极高,可以将请求封装为Promise数组,通过Promise.all保证最终渲染顺序和遍历顺序完全一致,不受请求响应速度影响:
const requestPromises = interval.map(async (range, i) => { // 预先生成日期数组和对应DOM节点 let days = []; let day = new Date(range[0].getTime()); for (; day <= range[1]; day.setDate(day.getDate() + 1)){ days.push(new Date(day).getDate()); } const chr = String.fromCharCode(97 + i); $('#charts').append(`<div style="width: 800px; height: 400px;"><canvas id="chart${chr}"></canvas></div>`); // 封装AJAX请求为Promise return new Promise(resolve => { $.ajax({ type: 'GET', url: "https://localhost:5001/currency/" + selectedCurrency, dataType: 'json', data: { StartDate: range[0].toISOString().split('T')[0], EndDate: range[1].toISOString().split('T')[0] }, success: (data) => resolve({chr, days, data}) }) }) }) // 所有请求返回后按原顺序渲染 Promise.all(requestPromises).then(chartList => { chartList.forEach(({chr, days, data}) => { const ctx = document.getElementById(`chart${chr}`).getContext('2d'); if(Chart.getChart(ctx)) Chart.getChart(ctx).destroy(); new Chart(ctx, { type: 'line', data: { labels: days, datasets: [{ label: label, borderColor: '#006241', borderWidth: 1, fill: true, data: data }] } }) }) })
内容的提问来源于stack exchange,提问作者Ferhad Mehdizade
相关产品推荐
相关产品推荐

