vue.js 更新数据时,出现数据更新,界面没有更新的情况【普通对象,不包含数组】
问题出现
代码如下:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>列表</title><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><style>body {padding: 20px;}</style></head><body><div id="app"><div><button @click="updateData()">更新张伟信息</button></div><div style="margin-top: 20px;"><div v-for="(item,index) in persons" :key="item.id"><span>{{item.name}}</span><span>-</span><span>{{item.age}}</span><span>-</span><span>{{item.gender}}</span></div></div></div></body><script>var vm = new Vue({el: '#app',data() {return {persons: [{id: 1,name: '张伟',age: 32,gender: '男'},{id: 2,name: '王芳',age: 28,gender: '女'},{id: 3,name: '李娜',age: 25,gender: '女'},{id: 4,name: '赵强',age: 40,gender: '男'},{id: 5,name: '刘洋',age: 30,gender: '男'},{id: 6,name: '陈静',age: 27,gender: '女'},{id: 7,name: '杨磊',age: 35,gender: '男'},{id: 8,name: '黄丽',age: 26,gender: '女'},{id: 9,name: '吴杰',age: 38,gender: '男'},{id: 10,name: '周敏',age: 29,gender: '女'}],}},mounted() {},methods:{updateData(){this.persons[0]={id: 1,name: '张伟111',age: 32,gender: '男'}console.log(this.persons)},},})</script>
</html>
按照希望的的情况,点击按钮的时候,第一行数据的name 应该从张伟 变成 张伟111 。但是实际情况?
数据更新的原理
Vue(尤其是 Vue 2 和 Vue 3)对数据变化的监测机制是其响应式系统的核心,理解这一机制有助于更高效地开发和调试 Vue 应用。下面将分别解释 Vue 2 和 Vue 3 的监测原理。
Vue 2 的数据监测原理(基于 Object.defineProperty)
核心原理:Object.defineProperty
Vue 2 通过递归地遍历对象的属性,并使用 Object.defineProperty 将其转化为“getter”和“setter”,从而拦截对数据的读取和修改。
Object.defineProperty(obj, 'key', {get() {// 收集依赖return value;},set(newVal) {// 触发更新value = newVal;}
});
Observer 模式
Vue 2 内部构建了一个 Observer 类,对每个对象进行监听。每当数据被读取时,收集当前组件(Watcher),当数据被修改时通知这些 Watcher 重新执行。
缺点与局限性
- 新增属性无响应:必须使用 Vue.set 才能让新属性响应式。
- 数组监听有限:只能监听数组的变异方法(如 push/pop/splice),无法拦截通过索引直接设置值(如 arr[0] = 1)。
Vue 3 的数据监测原理(基于 Proxy)
核心原理:Proxy
Vue 3 放弃了 Object.defineProperty,转而使用现代浏览器支持的 Proxy。它可以直接监听整个对象的操作,包括属性读取、设置、删除等,甚至能监听数组索引和新增属性。
const proxy = new Proxy(target, {get(target, key, receiver) {// 依赖收集return Reflect.get(target, key, receiver);},set(target, key, value, receiver) {// 派发更新return Reflect.set(target, key, value, receiver);}
});
优势
- 支持 数组索引 和 新增属性。
- 不再需要递归地遍历所有属性(延迟代理)。
- 可以更好地检测对象何时被访问或修改。
响应式系统的两个重要角色
Dep(依赖收集)
每个响应式属性都有一个依赖管理器(Dep),用来记录哪些 Watcher(组件或计算属性)使用了这个数据。
Watcher(依赖执行者)
当组件使用响应式数据时,会生成对应的 Watcher,并自动订阅这个数据的变化;当数据改变时,这些 Watcher 会被通知,从而触发组件更新或重新计算。
总结对比
特性 Vue 2 (defineProperty) | Vue 3 (Proxy) | |
---|---|---|
新增属性响应式 | ❌ 需手动 Vue.set | ✅ 自动监听 |
数组索引变更 | ❌ 无法监听 | ✅ 支持 |
性能 | 递归劣化性能 | 延迟代理,性能优 |
API 支持度 | 老浏览器兼容好 | 需现代浏览器 |
如果你想深入调试 Vue 的响应式系统,可以尝试查看源码中的以下文件:
- Vue 2:src/core/observer/index.js
- Vue 3:@vue/reactivity 包下的 reactive.ts
修改之后的代码
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>列表</title><script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><style>body {padding: 20px;}</style></head><body><div id="app"><div><button @click="updateData()">更新张伟信息</button></div><div style="margin-top: 20px;"><div v-for="(item,index) in persons" :key="item.id"><span>{{item.name}}</span><span>-</span><span>{{item.age}}</span><span>-</span><span>{{item.gender}}</span></div></div></div></body><script>var vm = new Vue({el: '#app',data() {return {persons: [{id: 1,name: '张伟',age: 32,gender: '男'},{id: 2,name: '王芳',age: 28,gender: '女'},{id: 3,name: '李娜',age: 25,gender: '女'},{id: 4,name: '赵强',age: 40,gender: '男'},{id: 5,name: '刘洋',age: 30,gender: '男'}],}},mounted() {},methods:{updateData(){this.$set(this.persons, 0, {id: 1,name: '张伟111',age: 32,gender: '男'})},},})</script>
</html>