问题出现
代码如下:
<!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>