上一篇咱们完成了第一步 (数据劫持),从而完成了对属性的监听,这一篇咱们来完成最后一步(发布-订阅)node
发布订阅的关系主要靠数组来维护,订阅就是将函数添加到数组,发布就是将数组中的函数执行bash
// 建立一个数组
function Dep() {
this.subs = []
}
Dep.prototype = {
// 将订阅内容添加到数组
addSub(fn) {
this.subs.push(fn)
},
// 每一个订阅内容都有一个 update 方法,通知内容发布
notify() {
this.subs.forEach(sub => sub.update())
}
}
function Watcher(fn) {
this.fn = fn
}
// 发布内容
Watcher.prototype.update = function () {
this.fn()
}
let watcher = new Watcher(() => console.log('娜美'))
let dep = new Dep()
dep.addSub(watcher)
dep.addSub(watcher)
dep.notify() // 娜美 ,娜美
复制代码
咱们的Dep构造函数不须要变更,须要先修改下 Wathermvvm
...
+ let hasWatcher = false // 添加一个开关 确保Watcher只实例化一次
if (node.nodeType === 3 && reg.test(txt)) {
function replaceTxt() {
node.textContent = txt.replace(reg, (matched, placeholder) => {
// 只有第一次编译才会实例化Watcher
+ if (!hasWatcher) {
+ new Watcher(vm, placeholder, replaceTxt); // 监听变化,进行匹配替换内容
+ hasWatcher = true
+ }
return placeholder.split('.').reduce((val, key) => {
return val[key];
}, vm);
});
};
// 替换
replaceTxt();
}
// 修改 Watcher
function Watcher(vm, exp, fn) {
this.fn = fn
+ this.arr = exp.split('.') // exp 是正则匹配后的字符串 a.b.c
+ this.vm = vm // vm 是mvvm的实例对象
+ Dep.target = this // 添加一个标识,用于收集订阅时的判断
// 经过访问 vm 的属性来触发对应属性的 get 如 vm.a.b.c, 从而收集当前的Wathcer
+ let val = vm
+ this.arr.forEach(key => {
+ val = val[key]
+ })
// 收集完订阅内容时置空
+ Dep.target = null
}
Watcher.prototype.update = function() {
this.fn();
}
复制代码
上面咱们说到经过属性的get方法来收集Watcher, 那么咱们还须要修改下Observe函数
// + 号表示新增代码
function Observe(data) {
+ let dep = new Dep(); // 实例化Dep ,建立一个用来存放Watcher的数组
for (let key in data) {
let val = data[key]
observe(val)
Object.defineProperty(data, key, {
configurable: true,
get() {
// 判断 Dep.target 存在,向数组中添加 Watcher
+ Dep.target && dep.addSub(Dep.target)
return val;
},
set(newVal) {
if (val === newVal) {
return;
}
console.log('数据被修改')
val = newVal;
observe(newVal);
+ dep.notify() // 数据被修改时 触发Watcher更新视图
}
})
}
}
复制代码
控制台修改name属性, 视图也发生了变化post
完整DEMO地址测试