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

Vue项目中Collection模式方法首次调用正常,二次调用报错非函数

问题分析与解决方案

问题根源

你的代码中,ExerciseCollection通过给原生数组挂载自定义方法后返回数组,但Vue 3在处理data中的数组时,会自动将其包装成响应式Proxy对象。这个代理只会保留原生数组的方法(如push、splice),你手动挂载的add、delete等自定义方法不会被代理继承。

首次调用add时,数组还未完全被Vue的响应式系统处理,方法存在;第二次调用时,数组已经变成了响应式Proxy,自定义方法丢失,因此抛出add is not a function错误。

另外,原集合代码还存在逻辑错误:this.indexOf(exercise)返回的是元素索引(数字),而非元素对象,所以editExercise.weight.splice(set, 1)这类代码会直接报错,因为数字没有weight属性。

修复方案:改用类实现集合

最可靠的方式是用ES6类来封装集合逻辑,类实例的方法不会被Vue的响应式代理破坏,同时能修正原有的逻辑错误:

1. 重写ExerciseCollection类

class ExerciseCollection {
  constructor(arr = []) {
    // 用items存储数组,避免直接操作原型
    this.items = arr;
  }

  add(exercise, date) {
    if (date) {
      exercise.date = date;
    }
    exercise.id = genId(date);
    this.items.push(exercise);
    return this;
  }

  delete(exercise) {
    const index = this.items.indexOf(exercise);
    if (index !== -1) {
      this.items.splice(index, 1);
    }
    return this;
  }

  deleteSet(exercise, set) {
    // 通过id查找目标元素,比indexOf更可靠(避免引用变化导致找不到)
    const targetExercise = this.items.find(item => item.id === exercise.id);
    if (targetExercise) {
      targetExercise.weight.splice(set, 1);
      targetExercise.reps.splice(set, 1);
      targetExercise.sets--;
    }
    return this;
  }

  addSet(exercise) {
    const targetExercise = this.items.find(item => item.id === exercise.id);
    if (targetExercise) {
      targetExercise.sets++;
    }
    return this;
  }
}

2. 修改Vue组件中的初始化代码

const app = Vue.createApp({
  data() {
    return {
      // 初始化集合并添加初始数据
      exerciseList: new ExerciseCollection().add({
        id: 0, title: 'Bench Press', date: '2024-04-03',
        sets: 2, reps: [10, 10], weight: [185, 185]
      }),
      dayList: new DayExerciseCollection(),
      reviewList: new ReviewCollection(),
      daysReview:{},
      selectedEditExercise: {},
      currentDay: ''
    };
  },
  methods: {
    addExercise(exercise, date){
      // 调用类实例的add方法
      this.exerciseList.add(exercise, date);
      this.dayList.add(exercise, date);
    },
    // 其他方法...
  }
});

为什么这个方案有效?

  • 类实例的方法属于实例本身,Vue的响应式代理会完整保留这些方法,不会丢失。
  • 用items属性存储数组,避免直接修改原生数组的原型,减少冲突风险。
  • 改用find通过id查找元素,比indexOf更可靠(如果元素引用变化,indexOf会找不到,但id是唯一标识)。

额外说明

如果一定要基于原生数组扩展,也可以将自定义方法添加到数组原型,但这种方式会污染全局数组,不推荐:

// 不推荐:污染全局数组原型
Array.prototype.exerciseAdd = function(exercise, date) {
  if (date) exercise.date = date;
  exercise.id = genId(date);
  this.push(exercise);
  return this;
}

内容的提问来源于stack exchange,提问作者JosefGungl

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 10:42:07