# 数据响应式
# 什么是数据响应式
无论是组件还是实例里, 数据都是放在data
选项里的. Vue 做到了使得数据能够被监听到改动, 并且触发重新渲染. 这就是数据响应式(reactivity system).
# 如何实现它
我们如此使用 Vue:
let state = { n: 0 };
const vm = new Vue({
data() {
return state;
}
});
2
3
4
5
6
那么 Vue 构造函数会对data
属性的对象进行修改, 使其能够响应式. 首先遍历对象的属性, 对于每个属性使用Object.defineProperty
方法覆盖之前的属性, 我们只能使用getter
&setter
来对其进行操作. 然后创建一个代理对象, 也使用Object.defineProperty
方法:
function proxy({data: {n: 0}}){
let value = data.n //存住n的值, 接下来的n将不会是现在的n了
Object.defineProperty(data, 'n', { // 之前data的n属性将由set n 和 get n 两个虚拟属性替代. 因此对n的修改会触发data的setter, 这样的改动是会被监听到的
get(){
return value
}
set(newVal){
value = newVal
}
})
let proxy = {}
Object.defineProperty(proxy, 'n', {
get(){
return data.n //调用data.n的getter
}
set(newVal){
data.n = newVal
}
})
return proxy
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
使用代理对象操作最后返回 proxy, 我们就不对原始对象进行操作了, 转而操作这个代理, 再由代理来去操作原始对象的数据. vm
对象就是一个代理, 我们可以使用vm.n = 1
来改变state.n
的值.因为它将触发代理的setter
, 然后更新data.n
. Vue 必须要知道data
里所有的改动, 因此才能没有遗漏地更新视图. 如果跳过代理, 或者不使用代理. 我们使用不合规范的方法对数据改动是不会被 Vue 察觉到, 进而无法更新视图.
# Object.defineProperty
的不足
我们知道, Vue 在实例化会调用Object.defineProperty
:
const vm = new Vue({
template: `
<div>
{{a}}
</div>
`
data(){
return {
a: 1
}
}
})
2
3
4
5
6
7
8
9
10
11
12
我们可以用vm.a = 2
改变数据, 但是vm.b = 3
则不会有任何改变. 因为vm.b
没有被监听, 也没有被代理. 对它的处理是无用功的. Vue 提供Vue.set()
和vm.$set()
来帮我们为没有提前声明的属性添加监听和代理, 而在Vue.set
方法里会帮我们调用Object.defineProperty
方法. 但是 Vue3 将会使用 ES6 的 Proxy 来取代Object.defineProperty
.相信会带来新的改变.
# 变异数组
当data
里的数据有数组时, 而且数组的 item 是不确定的, 比如要向后端请求具体的数组时. 会和上面的情况发生冲突: 所有数据需要一开始写在data
里, 然后才能监听变化. 数组里的 item 不能一开始就确定完, 从而无法被监听. 针对这种情况, Vue 提出了变异数组(mutation array)的解决方案. 它基于 JS 的 Array 内置方法封装了 7 个新的数组操作方法. 它在内部会调用对应的原生 Array 方法, 并且调用Vue.set
将新的 item 加入监听里. this.arr[1] = 12
这样的写法是无效的.