You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 18:06:01