// 只有先执行了依赖收集,才能在属性更新的时候派发更新
class Dep {
constructor() {
this.subs = []
}
//添加依赖
addSub(sub) {
this.subs.push(sub)
}
//更新
notify() {
this.subs.forEach(sub=>{
sub.update()
})
}
}
Dep.target = null
//当需要依赖收集的时候调用 addSub,当需要派发更新的时候调用 notify
//在组件挂载时会先对所有需要的属性调用 Object.defineProperty()
//然后实例化 Watcher,传入组件更新的回调
//在实例化的过程中会对模板的属性进行求值,触发依赖收集
class Watcher {
constructor(obj,key,cb) {
// 将 Dep.target 指向自己
// 然后触发属性的 getter 添加监听
//最后将 Dep.target 置空
Dep.target = this
this.cb = cb
this.obj = obj
this.key = key
this.value = obj[key] //此处触发属性的 getter
Dep.target = null
}
update() {
//获取新值
this.value = this.obj[this.key]
//调用 update 更新 Dom
this.cb(this.value)
}
}
//在执行构造函数时将 Dep.target 指向自身,
//从而收集到对应的 Watcher,在派发 更新的时候取出对应的 Watcher,然后执行 update 函数
function observe(obj) {
if(!obj || typeof obj !== 'object'){
return
}
for(let key in obj) {
defineReactive(obj,key,obj[key])
}
}
function defineReactive(obj,key,value) {
observe(value)
let dp = new Dep()
Object.defineProperty(obj,key,{
enumerable:true,
cinfigurable:true,
get:function() {
if(Dep.target) {
dp.addSub(Dep.target)
}
return value
},
set:function(newValue) {
value = newValue
dp.notify()
}
})
}
let obj = {a:1}
observe(obj)
new Watcher(obj,'a',function(v) {
//document.querySelector().innerText = v //更新 DOM
console.log("改变DOM")
})
obj.a = 3 //改变 DOM
我将最后一小段代码与上面分开,因为具体过程用最后一小段代码就可以逐步深入的分析。
首先是声明一个对象,这无需多说。
接着,调用 observe 方法,将 obj 作为参数传入。observe 首先判断 obj 是不是对象,遍历对象的键。然后通过 defineReactive 函数为每个对象的属性添加属性描述符,重点是改变每个属性的 getter 和 setter 行为。具体改变了什么行为我们下面再说。
接下来,新建一个 Watcher 对象,参数是对象、属性以及回调函数。这一部分较难理解。需要结合整体来看。新建 Watcher 对象时,首先调用 Watcher 类中的 constructor。其中先设置 Dep.target 为 this,接着代码中的 obj[key] 触发了 key 的 getter 行为。
在属性的 get 方法中,先检测 Dep.target 有没有值,如果有就通过 Dep 实例的 addSub 添加到实例的数组中,这一步叫做依赖收集。如果声明了多个 Watcher,它们都会被添加到这个实例的数组中,形成一个 Watcher 数组。
我们回到 Watcher 对象,这个对象还有一个 update 方法,调用回调函数并将 obj[key] 这个值传入。
最后对 obj.a 进行赋值时,会调用属性的 set 方法,在 set 后的函数中, Dep 实例的 notify 方法调用了 subs 数组中每个 Watcher 的 update 方法,而 update 方法会调用声明 Watcher 时传入的回调,回调中会进行 DOM 节点的更新。
NASA 2015-11-06 10-26-56 .jpg
网友评论