主要是双向绑定和依赖收集
Object.defineProperty,Vue.js就是基于它实现「响应式系统」的。
我们知道要实现双向绑定需要给对象通过Object.defineProperty添加getter和setter方法。那我们是怎么来设置vue的呢,我们可以实现一个observer函数,我们需要它接收一个对象,那个对象是需要对数据进行响应式的,然后遍历该对象的所有属性设置Object.defineProperty。
- 订阅者Dep (源码)
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
/*添加一个观察者对象*/
addSub (sub: Watcher) {
this.subs.push(sub)
}
/*移除一个观察者对象*/
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
/*依赖收集,当存在Dep.target的时候添加观察者对象*/
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/*通知所有订阅者*/
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
/*依赖收集完需要将Dep.target设为null,防止后面重复添加依赖。*/
Dep.target = null
- 观察者Watcher
class Watcher {
constructor () {
/* 在new一个Watcher对象时将该对象赋值给Dep.target,在get中会用到 */
Dep.target = this;
}
/* 更新视图的方法 */
update () {
console.log("视图更新啦~");
}
}
Dep.target = null;
// ps 注意这个target这个在源码里面有特殊的应用。
通过上面两个对象,我们可以看到订阅者和观察者,然后我们来定义一个defineProperty
function defineReactive (obj, key, val) {
/* 一个Dep类对象 */
const dep = new Dep();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
if (Dep.target) {
/*进行依赖收集*/
dep.depend()
}
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
/* 在set的时候触发dep的notify来通知所有的Watcher对象更新视图 */
dep.notify();
}
});
}
注意dep.depend()的条件,这是因为在第一次初始化的时候Dep.target为null,所以不会进行依赖收集,一种是数据观测阶段执行了defineReactive(),然后compile过程用到的数据会得以访问,同时会触发一次getter,然后加入依赖收集,另一种是computed(计算属性)调用的时候会会生成一个Watcher,同时会触发一次getter,然后加入依赖收集,整个vue项目里面只有这两种时候可以加入依赖收集。
watcher,在defineReactive()之后,compile解析指令的时候,会去创建一个watcher并绑定Update方法,添加到Dep对象上面。
然后之后我们就知道每一次修改对象状态的时候,都会触发setter方法,然后通过dep来通知加入依赖收集的watcher进行遍历_update()修改,然后通过diff算法 ,对比渲染到页面。
// 划重点,watcher是怎么设置Dep.target的,是通过生成watcher的时候,对全局的Dep对象加入了个target==_watcher,然后在dep.depend()的时候,把_watcher加入到闭包生成的dep数组里面。(这里面有一个Dep,和一个闭包dep)