上一篇文章实现了模板数据展示到视图上面,这一篇来实现数据的双向绑定。
Watcher
实现一个Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。
这里的实现需要结合发布订阅者模式。
1. Dep类的实现
完成对于依赖的收集和触发
class Dep{
constructor(){
this.subscribers = new Set()
}
addSub(sub){
this.subscribers.add(sub)
}
notify(){
this.subscribers.forEach(watcher => watcher.update());
}
}
2. 结合Observer
在Dep.target不为空的时,设置geter的时候收集依赖,在数据变化的时候触发更新
...
defindReactive(obj,key,value){
this.observer(value) //如果数据是一个对象,做成响应式
let dep = new Dep()
Object.defineProperty(obj,key,{
get(){
Dep.target && dep.addSub(Dep.target) // 添加
return value
},
set:()=>{
// 当赋的值和老值一样,就不重新赋值
if (newVal != value) {
this.observer(newVal)//新值,做成响应式
value = newVal
dep.notify() // 添加
}
}
})
}
...
3. Watcher类的实现
刚开始需要保存一个老的状态
class Watcher{
constructor(vm,expr,cb){
this.vm = vm
this.expr = expr
this.cb = cb
this.value = this.getVal() // 刚开始需要保存一个老的状态
}
getVal(){
Dep.target = this
let value = CompilerUtil.getVal(this.vm,this.expr) //在 new Watcher 保存旧值 触发 dep 加入观察
Dep.target = null
return value
}
// 当状态发生改变后,会调用观察者的update方法来更新视图
update(){
let newVal = CompilerUtil.getVal(this.vm,this.expr)
if(newVal !== this.value){
this.cb(newVal)
}
}
}
4. 结合Compiler
-
在上面1.2讲到,Dep.target不为空的时才会收集依赖,其实就是在new Watcher()的时候
-
这里就需要在模板数据发生改变以前,对data上面的数据实例化成一个个的Watcher加入依赖
-
模板数据发生改变的时刻,就是在Compiler中,使用CompilerUtil处理不同的指令的时候
分别修改CompilerUtil中的text和setTextContent函数
text(node,expr,vm,){
let fn = this.updater['textUpdater']
let content = this.getVal(vm, expr)
new Watcher(vm, expr, (newValue)=>{//1.4 添加
fn(node, newValue)
})
fn(node, content)
},
setTextContent(node,expr,vm) {
let fn = this.updater['textUpdater']
let content = expr.replace(/\{\{(.+?)\}\}/g, (...args) => {
new Watcher(vm, args[1], (newValue)=>{//1.4 添加
fn(node, newValue)
})
return this.getVal(vm, args[1])
})
fn(node, content)
},
简单测试一下 vm.address = ‘123’ 会发现似乎并没有任何变化,setter也为触发,其实_proxyData代理的时候还需要设置setter
_proxyData(data){
Object.keys(data).forEach((key)=>{
Object.defineProperty(this,key,{
get(){
return data[key]
},
set(newValue){//1.4 添加
return data[key] = newValue
}
})
})
}
5. v-model的实现
...
//1. html 添加输入框
<input type="text" v-model="school">
...
//2.CompilerUtil中添加model函数处理指令,同样需要加入依赖,并且触发依赖执行
//处理v-model指令
model(node,expr,vm){
let fn = this.updater['modelUpdater']
let content = this.getVal(vm, expr)
//加入依赖
new Watcher(vm, expr, (newVal) => {
fn(node, newVal)
})
node.addEventListener('input', (e)=>{
let value = e.target.value
this.setVal(vm,expr,value)//触发依赖
}, false)
fn(node, content)
}
...
// 3.updater对象中添加
modelUpdater(node,value){
node.value = value
}
...
//4.添加改变data数据值的方法
setVal(vm, expr, value) {
expr.split('.').reduce((data, key, index, arr) => {
if (index == arr.length - 1) {
return data[key] = value // 设置数据
}
return data[key]
}, vm.$data)
}
目前为止实现了指令v-mode对于数据的双向绑定,下一章将拆分v-model的语法糖模式,实现更多指令等;