interface ObjectData {
[key: string]: any
}
class Observer {
data: ObjectData
constructor(data: ObjectData) {
this.data = data
this.walk(data)
}
walk(data: ObjectData){
if(!data || typeof data !== 'object') {
return
}
// 每一层的每一个属性对应一个 Dep 实例。
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
defineReactive(obj: object, key: string, value: any) {
const dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if(Dep.target) {
dep.depend()
}
return value
},
set(newVal: any) {
if(newVal === value) {
return
}
value = newVal
dep.notify()
}
})
}
}
let watcherId = 0
class Wather {
getter
value: unknown
cb: any
watcherId: number
constructor(template: string, cb: any) {
this.watcherId = watcherId
watcherId ++
this.getter = parseTemplate(template)
this.cb = cb
this.value = this.get()
}
get() {
Dep.target = this
// 问题出在这里,每次获取都会触发一次 get,然后多收集了一次
const value = this.getter(wholeData)
Dep.target = null
return value
}
update() {
const oldValue = this.value
this.value = this.get()
this.cb(this.value, oldValue)
}
}
class Dep {
static target: any
subs: Wather[]
constructor() {
this.subs = []
}
addSub(sub: Wather) {
this.subs.push(sub)
}
depend() {
if(Dep.target) {
// 这一步很重要,不能重复收集
// 当
if(this.subs.findIndex(item => item.watcherId === Dep.target.watcherId) === -1) {
this.addSub(Dep.target)
}
}
}
notify() {
this.subs.forEach(sub => {
sub.update()
})
}
}
Dep.target = null
// 模板表达式解析方法
function parseTemplate(path: string) {
// path: 'name' || 'age'
// ['wholeData', 'name']
// ['wholeData', 'age']
const segments = path.split('.')
return function (obj: ObjectData) {
for(let i = 0; i < segments.length; i ++) {
if(!obj) {
return
}
obj = obj[segments[i]]
}
return obj
}
}
// test
const wholeData = {
name: '小王',
age: 23
}
// observe 只能监视对象的第一层属性。如果是嵌套属性需要递归监视。每一层的每一个属性对应一个 Dep 实例。
new Observer(wholeData)
// 一个 wather 对应一个在模板上使用到响应式对象属性的地方
// 监视用到 name 响应式数据的模板。当响应式数据改变时,触发回调
// 解析模板,用到了 wholeData.name 数据会触发被监听对象 wholeData 的 get 方法,将此处的 watcher 添加到一个数组里面。
new Wather('name', (value: unknown, oldValue: unknown) => {
console.error(`watcher1:name响应式数据改变了。老值:${oldValue},新值:${value}`)
})
new Wather('name', (value: unknown, oldValue: unknown) => {
console.error(`watcher2:name响应式数据改变了。老值:${oldValue},新值:${value}`)
})
// 改变 name 响应式数据,会触发对应 watcher 的notify 方法。
wholeData.name = '小军'
wholeData.name = '小红'
new Wather('age', (value: unknown, oldValue: unknown) => {
console.error(`age响应式数据改变了。老值:${oldValue},新值:${value}`)
})
// 改变 age 响应式数据。其实改变的时候,既会触发get,也会触发set。
// 原因是触发 set 后,去notify,去forEach 循环执行 wather的update方法。update方法又会触发get钩子,造成依赖重复
wholeData.age = 189
wholeData.age = 123
wholeData.age = 11
export default 1
Vue2 的响应式实现
于 2023-02-25 14:04:38 首次发布