场景代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<ul>
<li v-for="(item, index) in arr">{{item.name}}年龄为:{{item.age}}</li>
</ul>
<button @click="hendler">修改数据</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
let vm =new Vue({
el: '#app',
data: {
name: "小何程序员",
arr: [
{name: '1', age: 11},
{name: '2', age: 12},
{name: '3', age: 13},
{name: '4', age: 14},
]
},
methods: {
hendler() {
this.arr[0] = {name: '小何', age: 18}
console.log(this.arr)
}
},
})
</script>
</body>
</html>
问题描述
渲染的页面为
点击按钮输出如下
可以发现实质上data的数据已经被修改了但是页面却没有发生变化,响应式失效了
原因分析
对于data中的数据例如上面的arr,最终vue会利用defineProperty对这个数组设置响应式 如下
export function defineReactive(
obj: Object,
key: string,
val: any,
customSetter ? : ? Function,
shallow ? : boolean
) {
const dep = new Dep();
const property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return;
}
const getter = property && property.get;
const setter = property && property.set;
if ((!getter || setter) && arguments.length === 2) {
val = obj[key];
}
// 递归对每一个子元素设置响应式
let childOb = !shallow && observe(val);
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
// get 做依赖收集
get: function reactiveGetter() {
const value = getter ? getter.call(obj) : val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value;
},
// 主要做派发更新
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val;
if (newVal === value || (newVal !== newVal && value !== value)) {
return;
}
if (process.env.NODE_ENV !== "production" && customSetter) {
customSetter();
}
if (getter && !setter) return;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
}
childOb = !shallow && observe(newVal);
dep.notify();
},
});
}
此时我们只需知道这个方法的作用是利用defineProperty为arr设置get何set方法,get用于依赖收集,set用于派发更新,(如果你不了解依赖收集,只需知道依赖收集的目的是为了在用户对其重新赋值的时候能够使得页面更新,从而显示最新的数据)
而这句代码
let childOb = !shallow && observe(val);
的意思对于arr这里的话,就是对arr中的每一个子元素递归调用一次defineReactive方法,使得其子元素或者是孙元素等等都拥有set和get方法。
所以由源码可以知道,当你执行
this.arr[0] = {name: '小何', age: 18}
的时候并不会触发页面的更新,因为arr是引用数据类型,对其元素的改变并不会触发definePropety中的set方法,而在vue2中就是利用set方法重新更新页面的
但是如果你将代码改为如下
methods: {
hendle() {
// this.arr[0] = {name: '小何', age: 18}
this.arr[0].name = '小何'
console.log(this.arr)
}
},
由于vue对arr[0]中的name元素进行了依赖的收集(上文讲到递归调用了defineReactive方法),同时name是简单数据类型,这将触发该属性的set方法使得页面更新,显示最新的数据