正文
>
<
p
>
{{ JSON.stringify(this.testObj) }}
p
>
<
button
@
click
=
"set('a')"
>
设置testObj属性a
button
>
<
button
@
click
=
"set('b')"
>
设置testObj属性b
button
>
div
>
<
script
>
new
Vue({
el
:
'#app'
,
data
: {
testObj
: {},
},
watch
: {
'testObj.a'
() {
alert(
'a'
)
},
'testObj.b'
() {
alert(
'b'
)
},
},
methods
: {
set
(val) {
Vue.set(
this
.testObj, val,
true
);
}
},
})
script
>
body
>
html
>
答案是:
点a的时候alert a,点b的时候alert b。
如果再接着点a,点b,提示什么?
答案是:
没有提示。
先总结一下发现的现象:用Vue.set为对象o添加属性,如果添加的属性是一个对象,那么o的所有属性会被触发响应。
是不是不明白?且请听我讲解一下。
要回答上面这些问题,我们首先需要理解一下Vue的响应式原理。
从Vue官网这幅图上我们可以看出:当我们访问data里某个数据属性p时,会通过getter将这个属性对应的Watcher加入该属性的依赖列表;当我们修改属性p的值时,通过setter通知p依赖的Watcher触发相应的回调函数,从而让虚拟节点重新渲染。
所以响不响应关键是看依赖列表有没有这个属性的watcher。
为了把依赖列表和实际的数据结构联系起来,我画出了vue响应式的主要数据结构,箭头表示它们之间的包含关系:
Vue里的依赖就是一个Dep对象,它内部有一个subs数组,这个数组里每个元素都是一个Watcher,分别对应对象的每个属性。Dep对象里的这个subs数组就是依赖列表。
从图中我们可以看到这个Dep对象来自于__ob__对象的dep属性,这个__ob__对象又是怎么来的呢?这就是我们new Vue对象时候Vue初始化做的工作了。Vue初始化最重要的工作就是让对象的每个属性成为响应式,具体则是通过observe函数对每个属性调用下面的defineReactive来完成的:
/**
* Define a reactive property on an Object.
*/
function defineReactive (
obj,
key,
val,
customSetter,
shallow
) {
var dep = new Dep();
var property = Object.getOwnPropertyDescriptor(obj, key);
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
var getter = property && property.get;
if