标准答案
- Vue 2 初始化组件 data 时,会遍历对象已有属性并用 Object.defineProperty 转成 getter / setter。
- 如果后续直接新增 this.user.age = 18,这个 age 没有在初始化时被劫持,Vue 2 就无法自动收集和触发依赖。
- 数组直接按索引赋值 this.list[index] = item,或直接改 this.list.length,也可能无法触发视图更新。
- Vue.set(target, key, value) 或 this.$set(target, key, value) 会给对象新增响应式属性,并通知依赖更新。
- 数组场景通常优先用 splice、push、pop、shift、unshift、sort、reverse 这类 Vue 2 能拦截的变更方法。
题目解析
这个问题来自 defineProperty 的拦截粒度。Vue 2 能监听已经定义过 getter / setter 的属性,但不能自动知道一个普通对象新增了哪个属性。
Vue.set 不是普通赋值的语法糖,它会走 Vue 2 内部的 defineReactive 和依赖通知流程。
Vue 3 使用 Proxy 后,这类对象新增和数组索引问题明显减少,但老项目、Vue 2 组件库和迁移项目仍然经常考。
代码示例
Vue 2 老项目中,对象新增属性用 $set,数组替换项用 splice 更稳。
JavaScript
// 对象新增属性
this.$set(this.user, 'age', 18)
// 数组替换某一项
this.items.splice(index, 1, nextItem)
// 不推荐依赖这类直接写法触发 Vue 2 更新
this.user.age = 18
this.items[index] = nextItem
this.items.length = 0常见误区
- 认为 Vue.set 只是为了写法统一,不知道它负责补响应式和通知更新。
- Vue 2 数组更新全部用索引赋值,导致视图偶发不更新。
- 把 Vue 3 的 Proxy 行为套回 Vue 2 老项目,忽略两套响应式实现的差异。