上一篇 https://blog.csdn.net/qq_38765789/article/details/99952976 讲到数据劫持部分, 这一部分实现一个订阅者
1.需要一个可以容纳订阅者的消息订阅器Dep
function Dep(){
this.subs = [];
}
Dep.prototype = {
addSub:function(sub){
this.subs.push(sub);
},
notify:function(){
// 依次通知
this.subs.forEach(sub=>{
sub.update();
})
}
}
2.订阅者
// vm是vue实例
// exp指令的属性值 比如v-model='name'的name
// cb是更新函数
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
this.value = this.get(); // 将自己添加到订阅器的操作
}
Watcher.prototype = {
update: function() {
var value = this.vm.data[this.exp]; //data.name 可能已经更改的数据
var oldVal = this.value; // 添加订阅者时初始化的数据
// 判断是否需要更新
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
get: function() {
Dep.target = this; // 缓存自己
var value = this.vm.data[this.exp] // selfVue.data['name'] 触发Observe中的get
Dep.target = null; // 释放自己 由于target是Dep的原型变量,以免影响其他观察者
return value;
}
};
3. 修改观察者(发布者)部分
function defineProxy(obj,key,value){
// 递归所有子属性
Observe(value);
// 对于每一个属性,都有一个订阅者数组
let dep = new Dep();
// 每调用一次defineProxy,就产生一个该函数上下文,保存这个val
let val = value;
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
set(newVal){
if(val === newVal){
return;
}
console.log(`${val}=>${newVal}`)
val = newVal;
dep.notify(); //通知订阅者
},
get(){
if(Dep.target){
dep.addSub(Dep.target)
}
return val;
}
})
}
function Observe(data){
if (!data || typeof data !== 'object') {
return;
}
Object.keys(data).forEach(function(key) {
defineProxy(data, key, data[key]);
});
}
4.初始化Vue实例
function SelfVue (data, el, exp) {
this.data = data;
Observe(data);
el.innerHTML = this.data[exp];// 初始化模板数据的值 exp:data.name
// 添加订阅者
new Watcher(this, exp, function (value) {
el.innerHTML = value;
});
return this;
}
var ele = document.querySelector('#name');
var selfVue = new SelfVue({
name: 'hello world'
}, ele, 'name');
function changeName(){
selfVue.data.name = document.querySelector('#editName').value; //触发data的set拦截器
}
5.测试
这样就实现了一个简单的双向绑定,但还是需要一个模板解析器compile,用来解析所有结点,并将需要绑定数据的结点绑定为订阅者。具体可以参考https://github.com/canfoo/self-vue/tree/master/v2