开始
声明一个对象 man,可以视为 vue 中的 data
let man = {
height: 180,
weight: 70,
wealth: 100000000
}
添加 Observer
作用在于将参数对象的属性变为响应式,只要对象的属性被读取或者被修改都能观察到。然后新建一个 Observer 实例,将 man 作为参数扔进去。这里的 proxyData 是将 man 的属性代理到以 man 为参数的 Observer 实例上去。
class Observer {
constructor(obj) {
this.walk(obj)
}
walk(obj) {
Object.keys(obj).forEach(prop => {
this[prop] = obj[prop]
this.proxyData(obj, prop)
this.defineReactive(this, prop, obj[prop])
})
}
proxyData(obj, prop) {
let _this = this
Object.defineProperty(obj, prop, {
get() {
return _this[prop]
},
set(newVal) {
_this[prop] = newVal
}
})
}
defineReactive(obj, prop, val) {
Object.defineProperty(obj, prop, {
get() {
console.log(`${prop} – 被读取!`)
return val
},
set(newVal) {
if (newVal == val) return
val = newVal
console.log(`${prop} – 被修改!`)
}
})
}
}
new Observer(man)
这时打印一下 man
现在 man 的属性都是由 Observer 实例所对应的属性的 getter 来返回,只有在查看时会被触发
对 man 的属性进行修改也会触发实例对应属性的 setter
添加 Watcher
现在的 Watcher 有点像 vue 中的 computed,实际上就是定义一个计算属性,这个计算属性依赖于前面 man 中的某些属性,由他们计算而得。
class Watcher {
constructor(obj, prop, computed) {
this.getVal(obj, prop, computed)
}
getVal(obj, prop, computed) {
Object.defineProperty(obj, prop, {
get() {
console.log(`computed 属性 – ${prop} 被读取!`)
return computed()
},
set() {
console.error(‘ 计算属性不可被修改!’)
}
})
}
}
new Watcher(man, ‘strength’, () => {
let {height, weight} = man
if (height > 160 && weight > 70) return ‘strong’
return ‘weak’
})
看起来没什么问题,所依赖的属性如果变了,计算属性只要再被查看(get 方法)一次就可以更新了。但 vue 中的视图渲染是实时的,视图层依赖于数据层,数据变化了,视图层也会跟着变化,不需要手动更新。类比到这个例子就是计算属性如何才能在其所依赖的属性发生变化时被通知从而触发应有的事件?
这时我们先给 Watcher 加多一个 callback,用于处理当依赖的数据被修改时,我这个计算属性该怎么响应
比如当依赖被修改时,我们就把这个计算属性的值打印出来
class Watcher {
constructor(obj, prop, computed, callback) {
this.getVal(obj, prop, computed, callback)
}
new Watcher(man, ‘strength’, () => {
let {height, weight} = man
if (height > 160 && weight > 70) return ‘strong’
return ‘weak’
}, () => {
console.log(`i am so ${man.strength} !`)
})
一切都准备好了,接下来就是该如何实现?
我们先看下 Watcher 中 getVal 这个方法
getVal(obj, prop, computed, callback) {
Object.defineProperty(obj, prop, {
get() {
console.log(`computed 属性 – ${prop} 被读取!`)
return computed()
},
set() {
console.error(‘ 计算属性不可被修改!’)
}
})
}
当我们查看计算属性时,会调用 computed 这个方法,相当于查看了其所依赖的 height 和 weight 属性,而在上面我们已经让 man 的所有属性都拥有了 get 方法,即他们被查看时我们是不是可以把 callback 塞给他们?这时候我们引进一个桥梁,来连接 Watcher 和 Observer。
添加 Dep
Dep 的用处在于当某一个属性(以下称‘自己’)被依赖了,将依赖自己的粉丝(们)– 也就是 Watcher(s),收集起来,假如自己发生了变化,能够及时通知粉丝们。
class Dep {
constructor() {
this.deps = []
}
getDeps() {
if (!Dep.target || this.deps.includes(Dep.target)) return
console.log(‘ 依赖添加 ’, Dep.target)
this.deps.push(Dep.target)
}
notify() {
this.deps.forEach(dep => {
dep()
})
}
}
这里的 Dep.target 就是前面所说的 callback 方法了。这时我们改一下 Watcher 中的 getVal
getVal(obj, prop, computed, callback) {
Object.defineProperty(obj, prop, {
get() {
Dep.target = callback
console.log(`computed 属性 – ${prop} 被读取!`)
return computed()
},
set() {
console.error(‘ 计算属性不可被修改!’)
}
})
}
在计算属性被查看时,将 callback 赋值给 Dep.target,接下来就会调用其所依赖属性的 getter,我们只要在 getter 里把 callback 给收集起来就行了。接下来修改依赖属性的 getter 方法。
defineReactive(obj, prop, val) {
let dep = new Dep()
Object.defineProperty(obj, prop, {
get() {
console.log(`${prop} – 被读取!`)
dep.getDeps() // 依赖收集
return val
},
set(newVal) {
if (newVal == val) return
val = newVal
console.log(`${prop} – 被修改!`)
}
})
}
这时 watcher 的 callback 都被依赖属性给收集起来了,当依赖属性发生变化时只要去运行这些 callback 就可以了。接下来就是修改依赖属性的 setter 方法。
defineReactive(obj, prop, val) {
let dep = new Dep()
Object.defineProperty(obj, prop, {
get() {
console.log(`${prop} – 被读取!`)
dep.getDeps()
return val
},
set(newVal) {
if (newVal == val) return
val = newVal
console.log(`${prop} – 被修改!`)
dep.notify() // 运行所有 callback
}
})
}
运行看看
我们加多一个 Watcher 试试
new Watcher(man, ‘isGreat’, () => {
let {height, weight, wealth} = man
if (height > 180 && weight > 70 && wealth > 100000) return ‘Great!’
return ‘not good enough …’
}, () => {
console.log(`they say i am ${man.isGreat}`)
})
这就是 vue 中的一个依赖对应多个 Watcher