Vue: 深入理解数据响应式
通过这篇文章,深入理解一下options.data,方便之后学习options的进阶属性。
主要原理在文档的深入响应式原理章节可以找到,但是本篇文章扩充了非常多的内容。
Vue 对 data 做了什么
重要议题
小实验 - data 变了
- 示例代码:
myData
居然变了 - 一开始是
{n:0}
,传给new Vue
之后立马变成{n: (...)}
{n: (...)}
是个什么玩意,为什么表现和{n:0}
一致?
我们需要先看一下ES6的getter 和 setter
- 示例代码
- 学完之后呢?还是不理解
{n: (...)}
呀。
我们需要再看一下Object.defineProperty() | MDN
- 看完了,还是不理解:(
- 继续试验
现在理解了吗?
示意图
如果data有多个属性n、m、k,那么就会有
get n
,get m
,get k
等
getter 和 setter
定义的时候可以直接去设置一个getter和setter
来两个例子:
const obj = {
log: ["a", "b", "c"],
get latest() {
if (this.log.length === 0) {
return undefined;
}
return this.log[this.log.length - 1];
},
};
console.log(obj.latest);
// expected output: "c"
const language = {
set current(name) {
this.log.push(name);
},
log: [],
};
language.current = "EN";
language.current = "CN";
console.log(language.log);
// expected output: Array ["EN", "CN"]
Object.defineProperty
定义完了之后,你又突然想加一个getter和setter,就可以使用
Object.defineProperty
小结
Object.defineProperty
- 可以给对象添加属性value
- 可以给对象添加getter / setter
- getter / setter用于对属性的读写进行监控
代理和监听
- 对myData对象的属性读写,全权由另一个对象vm负责
- 那么vm就是myData的代理
- 比如不用myData.n,偏要用vm.n来操作myData.n
vm = new Vue({data: mydata})
- 首先,会让vm成为myData的代理(proxy)
- 其次,会对myData的所有属性进行监控
Q: 为什么要监控?
A: 好问题,为了防止myData的属性变了,而vm不知道。
Q: vm知道了又如何?
A: 你是不是傻,知道属性变了,就可以调用
render(data)
了呀。
UI = render(data)
数据响应式
什么是响应式
- 如果一个物体能对外界的刺激做出反应,它就是响应式的
我打你一拳,你会喊疼,那你就是响应式的:)
Vue 的 data 是响应式
const vm = new Vue({data:{n:0}})
- 如果修改vm.n,那么UI中的n就会响应
- Vue 2通过Object.defineProperty来实现数据响应式
什么是响应式网页
- 如果用户改变窗口大小,网页内容会做出响应,那这个网页就是响应式网页。
比如著名的响应式网页: Smashing Magazine
Vue 的 data 的 bug
Object.defineProperty 的问题
Q: 对于 Object.defineProperty(obj, 'n', {...})
,必须要有一个n,才能监听和代理obj.n对吧,可是如果前端开发者差点意思,没有给n怎么办?比如下面这两个例子:
示例一: Vue会给出一个警告
示例二: Vue只会检查第一层属性
Q: 那此时如果我点击set b,请问视图中会显示1吗?
A: 不会。因为Vue没有办法监听一开始就不存在的obj.b
那怎么办
-
我把 key 都声明好,后面不再加属性不就行了
-
使用
Vue.set
或者this.$set
Vue.set 和 this.$set
- 作用一: 新增key
- 作用二: 触发UI更新(但不会立刻更新)
- 举个例子:
console.log(Vue.set === this.$set);
Vue.set(this.obj, "b", 1);
this.$set(this.obj, "b", 1);
data 中有数组怎么办
Q: 刚才说了如果data中新增了key有两种方法可以解决,但有没有可能没有办法提前声明好,只能使用set呢?比如说data里有数组怎么办?
A: 好问题,继续看。
- 你没法提前声明所有key:
示例 1: 数组的长度可以一直增加,下标就是key,你看,你没有办法提前把数组的key都声明出来,Vue也不能检测对你新增了下标,难道每次改数组都要用Vue.set
或者this.$set
?
- 尤雨溪的做法:
篡改数组的API,见文档中变更方法,这 7 个API都会被Vue篡改,调用后会更新UI.
- 篡改的ES6写法:
不代表Vue的真实实现,只是说个思路
class VueArray extends Array {
push(...args) {
const oldLength = this.length; // this就是当前数组
super.push(...args); //会调用上一层原型(原来数组)的push方法
console.log(" push ");
for (let i = oldLength; i < this.length; i++) {
Vue.set(this, i, this[i]); // 将每个新增的key都告诉Vue
}
}
}
可以看到,Vue中的push()做了两件事,第一个是调用以前的push(),同时还帮你把新增的key通过
Vue.set
或者this.$set
通知给Vue。
- 结果:
你还是可以通过this.array.push()
就可以完成对数组增的操作,但其实你要明白,这个push() 其实已经不是最原始的那个push() 了。
你: push() 你变了
push(): 没错,我变了。但是,你还是可以像以前对我那样对我[旺柴]
总结
对象中新增的 key
- Vue没有办法事先监听和代理
- 要使用set来新增key,创建监听和代理,更新UI
- 最好提前把属性都写出来,不要新增key
- 但数组做不到 “不新增key”
数组中新增的 key
- 也可以用set来新增key,更新UI
- 不过尤雨溪篡改了 7 个API方便你对数组进行增删
- 这 7 个API会自动处理监听和代理,并更新UI
注意,
this.$set
作用于数组时,并不会自动添加监听和代理,原因未知,可以问尤雨溪:)
使用 Vue 提供的变更方法 API 时,会自动添加监听和代理
说说对 Vue 数据响应式的理解
好了,写了这么多,最后说说我对Vue数据响应式的理解
- Vue通过使用getter&setter和Object.defineProperty来追踪data的变化,这样来实现响应式
- Vue通过使用
Vue.set
或者this.$set
来新增key,并且创建监听和代理,触发UI更新(不会立即更新)
扩展阅读: Vue 数据响应式中的一个刁钻的题 - 知乎