# GitHub 地址
2021/02/10
# 实现过程:
# 发布订阅的关键步骤
这里发布订阅触发的前后步骤:从MVue里开始
new Observer
的时候给所有数据绑定了getter/setter
,同时每一层数据 data 都有一个dep
实例new Compile
的时候编译节点触发compileUtil
里 执行new Watcher
new Watcher
里的getOldVal
先Dep.target = this
,把wathcer
自身绑到Dep
类上,然后调用compileUtil.getVal
getVal
里有操作data[curVal]
,会触发这个data[curVal]
的getter
getter
里判断执行Dep.target && dep.addSub(Dep.target)
【Dep.target 的值第3步加上的】,这样dep
实例的subs
就有了一个watcher
- 回到
watcher
的getOldVal
里,要把Dep.target
置null
,否则下次谁再执行getter
的时候,Dep.target
里就有其他的wathcers
,然后这些wathcers
会被添加进别人的dep.subs
里
# Observer
class Observer {
constructor(data) {
this.observer(data)
}
observer(data) {
if (data && typeof data === "object") {
Object.keys(data).forEach(key => {
this.defineReactive(data, key, data[key])
})
}
}
defineReactive(obj, key, value) {
// 递归遍历
this.observer(value)
// ★★★每一层数据 data 都要有一个 dep
const dep = new Dep()
// 劫持所有的 data
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get() {
// ★★★如果这个时候已经有Dep.target,则添加进subs
Dep.target && dep.addSub(Dep.target)
return value
},
/**
* 1. 调用 this.observer(newVal) 是为了 防止给对象赋值时没有给新的值 设置getter/setter
* 比如 vm.$data.person={a:1} 整个 person 的指向就改了,需要重新给这个新对象设置一下
* 2. setter 用箭头函数?因为 this.observer 的 this 应该指向类实例,不用箭头函数就会指向
* Object.defineProperty 中的 Object
*/
set: (newVal) => {
if (newVal !== value) {
this.observer(newVal)
value = newVal
}
// 每次更改数据都要通知Dep更新
dep.notify()
}
})
}
}
# Dep
// 发布
class Dep {
constructor() {
// 用来收集 watchers
this.subs = []
}
// 收集观察者
addSub(watcher) {
this.subs.push(watcher)
}
// 改值的时候,通知观察者去更新
notify() {
console.trace("notify通知观察者,当前watcher:", this.subs);
this.subs.forEach(w => w.update())
}
}
# Watcher
class Watcher {
// 为了在更新的时候需要做新旧值判断,然后对节点
constructor(vm, expr, callback) {
this.vm = vm
this.expr = expr
this.callback = callback
// 旧值
this.oldVal = this.getOldVal()
}
// new 的时候获取老的值赋值给 watcher
getOldVal() {
// ★★★这里很关键,在new Watcher的时候,把这个wathcer挂载到Dep类上,
Dep.target = this
const oldVal = compileUtil.getVal(this.expr, this.vm)
Dep.target = null
return oldVal
}
// 更新,是 Dep 的 notify 调用,调用时肯定是产生了数据变化
update() {
// 获取新值
const newVal = compileUtil.getVal(this.expr, this.vm)
if (newVal !== this.oldVal) {
this.callback(newVal)
}
}
}
# 代理 vm.$data 原理
在 MVue
里遍历 data 的 key(第一层的就行),也是劫持方式
Object.defineProperty(this, key, {
get() {
// 当输入 this.msg/vm.msg 的时候,本身没有msg这个属性,我们劫持他返回$data.msg
return data[key]
},
set(val) {
data[key] = val
}
})
# 对 mustache 文本节点 {{xx}} 的处理
因为可能存在形如 {{person.name}} --- {{person.age}}
这种形式存在,假设触发 person.name
的 watcher
,但是不能直接调用 updater.textUpdater(node, newVal)
,这样会把原来假设是 xiaoming --- 18
替换成 lisi
(整体被一个 person.name
的新值 lisi
替换了)。
所以我们调用 getContent(expr, vm)
,对整个节点做处理替换,这里传入的 expr
就是 {{person.name}} --- {{person.age}}
` ,会循环遍历里面的每一个值去取当前的值去进行替换
getContent(expr, vm) {
return expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
return this.getVal(args[1], vm)
})
}