聊聊 options 中的 data。
Key Points
- Vue 的 data 是响应式的,如果改变 data,那么 UI 就会刷新
- Vue 2 通过 Object.defineProperty 实现数据响应式
- Vue 2 会将原来的属性变为 getter-setter 属性,并创建一个代理来操纵数据
getter 和 setter
由于 getter 和 setter 是 Vue 数据响应式中运用到的非常重要的一环,我们得先把这两个东西搞清楚。
通过下面这段代码,我们可以很清楚地了解到 get 和 set 其实就是两个函数:get 返回了一个拼凑的字符串,set 则修改了 person 内部的 firstName 和 lastName 两个真实存在的属性——但是这两个函数的定义和调用稍稍有点特别。
|
|
我们可以看到 person
中的 name
表示得有点特别,事实上他并不是一个真实存在的属性。
|
|
Object.defineProperty
我们可以使用 Object.defineProperty()
方法来给对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象,详细的用法可以参照 MDN 文档,我们这里只介绍影响后续理解的部分。
基本语法是:
|
|
这里需要介绍一个概念—— 描述符。目前我们的描述符有两种:数据描述符 和 存取描述符。
数据描述符 是一个具有值的属性,该值可能是可写的,也可能不是可写的——也就是类似于我们常见的那种普通属性。
1 2 3 4 5 6
let data = {} Object.defineProperty(data, 'n', { value:0 // 数据描述符,这里的意思是:给 data 定义一个新属性 n,他的值为 0 })
存取描述符 是由 getter-setter 函数对描述的属性——也就是上文说的并不真实存在的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13
let data = {} data._n = 0 // _n 用来存实际上不存在的 n 的值 Object.defineProperty(data, 'n', { get(){ // getter-setter 函数是存取描述符,可以用来在存取的时候做校验 return this._n }, set(value){ if (value < 0) return this._n = value } })
至此,我们学会了使用 getter-setter 来为函数新增属性,但是我们可能还需要借助代理,并且监听原来的属性来防止直接对 this._n
进行篡改。
|
|
Vue 中是怎么做的
|
|
从原理上讲其实是差不多的,Vue 拿到我们传入的 myData 之后,做了这么几件事情:
- 让
vm
成为myData
的代理,以后我们用vm.xxx
或者this.xxx
就可以直接操作原来在myData
中的数据了; - 删除掉
myData
上原来的所有属性,并改成 getter-setter 属性,防止 myData 上面的属性被越过vm
直接篡改; - 这么做的好处是可以让
vm
知道属性变化之后触发render
问题
事实上因为 Object.defineProperty()
其实是有一些问题的,因此 Vue 中也会存在这些问题,尽管 Vue 已经对他们进行了处理,但仍然应当注意。
如果最开始属性不存在,后来想要加属性,那么新加的属性就没有被 getter-setter 化,因此就不具备响应式。
1 2 3 4 5 6 7 8
const vm = new Vue({ data: { a: 1 } }) vm.b = 2 // vm.b 不是响应式的
解决方法:要么最开始就把所有的属性名写好,要么就使用 Vue.set(this.obj, 'key', 'value')
或者 vm.$set(this.obj, 'key', value)
来添加新的属性。
- 对于数组来说,使用
arr[index] = value
来添加值将同样不会被检测到。
解决办法:Vue 实际上已经给我们的数组加上了一层新的原型,并提供了 push
pop
shift
unshift
splice
sort
reverse
这个 7 个常用的 API。