Object.defineProperty
Vue的响应式原理最底层是通过 Object.defineProperty 的 get 和 set
实现的。
function defineReactive (obj, key, val) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被读了,我要不要做点什么好?');
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
console.log("数据被改变了,我要把新的值渲染到页面上去!");
}
})
}
Observer
Observer 类用于把对象的每个属性进行 Object.defineProperty 响应式绑定。
class Observer {
constructor() {
observe(this.data);
}
}
export function observe (data) {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
// 将data中我们定义的每个属性进行响应式绑定
defineReactive(obj, keys[i]);
}
}
Dep 「依赖管理」
什么是依赖?
Dep 就是跟响应式属性相关的依赖,当响应式属性值更新时要通知这些依赖进行更新,每个响应式属性都有一个 Dep 来管理它的依赖。
比如下面的代码案例,我们发现,虽然 data
中有 text
和 message
属性,但是只有 message
被渲染到页面上,至于 text
无论怎么变化都影响不到视图的展示,因此我们仅仅对 message
进行收集即可,可以避免一些无用的工作。
那这个时候 message
的 Dep
就收集到了一个依赖,这个依赖就是用来管理 data
中 message
变化的。
<div>
<p>{{message}}</p>
</div>
data: {
text: 'hello world',
message: 'hello vue',
}
当使用watch
属性时,比如监听message
的变化,message
变化时我们就要通知到watch
这个钩子,让它去执行回调函数。
这个时候message
的Dep
就收集到了两个依赖,第二个依赖就是用来管理watch
中message
变化的。
watch: {
message: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
}
当开发者自定义computed
计算属性时,如下messageT
属性,是依赖message
的变化的。因此message
变化时我们也要通知到computed
,让它去执行回调函数。 这个时候message
的Dep
就收集到了三个依赖,这个依赖就是用来管理computed
中message
变化的。
computed: {
messageT() {
return this.message + '!';
}
}
如何收集依赖
我们如何知道data
中的某个属性被使用了,答案就是Object.defineProperty
,因为读取某个属性就会触发get
方法。可以将代码进行如下改造:
function defineReactive (obj, key, val) {
let Dep; // 依赖
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: () => {
console.log('我被读了,我要不要做点什么好?');
// 被读取了,将这个依赖收集起来
Dep.depend(); // 本次新增
return val;
},
set: newVal => {
if (val === newVal) {
return;
}
val = newVal;
// 被改变了,通知依赖去更新
Dep.notify(); // 本次新增
console.log("数据被改变了,我要把新的值渲染到页面上去!");
}
})
}
Watcher 「中介」
依赖的本质是一个个 Watcher,而 Watcher
就是类似中介的角色,比如message
就有三个中介,当message
变化,就通知这三个中介,他们就去执行各自需要做的变化。
Watcher
能够控制自己属于哪个,是data
中的属性的还是watch
,或者是computed
,Watcher
自己有统一的更新入口,只要你通知它,就会执行对应的更新方法。
因此我们可以推测出,Watcher
必须要有的2个方法。一个就是通知变化,另一个就是被收集起来到Dep中去。
class Watcher {
addDep() {
// 我这个Watcher要被塞到Dep里去了~~
},
update() {
// Dep通知我更新呢~~
},
}