1. Watcher
类创建
在编写Watcher
类之前,我们先来看一张图,理解一下Dep
与Watcher
的关系
通过前面的学习,我们知道在Observer
类中为每一个响应式的数据创建了Dep
对象,而且在getter
中会收集依赖,所谓收集依赖就是将watcher
观察者添加到subs
数组中.
而在setter
中会触发依赖,其实就是调用Dep
对象中notify
方法,该方法会获取subs
数组中的所有的watcher
,然后执行watcher
中的update
方法来更新对应的视图。
Watcher
类的代码如下:
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
//data中的属性名称
this.key = key;
//回调函数负责更新视图
this.cb = cb;
//获取更新前的旧值
this.oldValue = vm[key];
}
// 当数据发生变化的时候更新视图
update() {
//只要update方法调用,获取到的值就是新值,因为当数据发生了变化,才会调用该方法
let newValue = this.vm[this.key];
if (newValue === this.oldValue) {
return;
}
//调用cb回调函数更新视图,将新值传递到该回调函数中
this.cb(newValue);
}
}
接下来还有一件事情需要处理一下:
当创建了·Watcher
对象后,需要将当前创建的Watcher
对象添加到Dep
中的subs
数组中。
我们可以查看Observer
类,在get
方法中已经写过将Watcher
对象添加到Dep
中的subs
数组中了(Dep.target && dep.addSub(Dep.target);
),但是
问题是,我们并没有创建target
属性,所以下面我们创建一下target
属性。
下面在Watcher
类的构造方法中,添加给Dep
添加target
属性,用来保存Watcher
的实例。
class Watcher {
constructor(vm, key, cb) {
this.vm = vm;
//data中的属性名称
this.key = key;
//回调函数负责更新视图
this.cb = cb;
// 把watcher对象记录添加到Dep类的静态属性target上.
Dep.target = this;
//触发get方法,因为在get方法中会调用addSub方法(下面我们通过vm来获取key对应的值的时候,就执行了get方法,因为我们已经将data属性编程了响应式,为其添加了`getter/setter`).
//获取更新前的旧值
this.oldValue = vm[key];
Dep.target = null; //防止以后重复性的添加
}
}
以上内容需要重点去体会.
2. 创建Watcher
对象
下面来看一下关于Watcher
对象的创建。
// 编译文本节点,处理差值表达式
compileText(node) {
// console.dir(node);
// {{ msg }}
//我们是用data中的属性值替换掉大括号中的内容
let reg = /\{\{(.+)\}\}/;
//获取文本节点的内容
let value = node.textContent;
//判断文本节点的内容是否能够匹配正则表达式
if (reg.test(value)) {
//获取插值表达式中的变量名,去掉空格($1 表示获取第一个分组的内容。)
let key = RegExp.$1.trim();
//根据变量名,获取data中的具体值,然后替换掉差值表达式中的变量名.
node.textContent = value.replace(reg, this.vm[key]);
//创建Watcher对象,当数据发生变化后,更新视图
new Watcher(this.vm, key, (newValue) => {
//newValue是更新后的值
node.textContent = newValue;
});
}
}
下面要在index.html
文件中导入相关的js
文件。
<script src="./js/dep.js"></script>
<script src="./js/watcher.js"></script>
<script src="./js/compiler.js"></script>
<script src="./js/observer.js"></script>
<script src="./js/vue.js"></script>
注意:以上导入文件的顺序,由于在watcher.js
文件中使用了dep.js
文件中的内容,所以先导入dep
,同样在compiler.js
文件中使用了watcher.js
文件中内容,所以先导入了watcher.js
.
下面可以进行测试了。
先将index.html
文件中的,如下语句注释掉:
vm.msg = { text: "abc" };
然后,打开浏览器的控制台,输入如下内容
vm.msg="abc"
对应的页面视图中的内容也发生了变化。这也就实现了响应式机制,所谓响应式就是当数据变化了,对应的视图也会进行更新。
所以需要在textUpdater
和modelUpdater
方法中完成Watcher
对象的创建。
//处理v-text指令
textUpdater(node, value, key) {
node.textContent = value;
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue;
});
}
//处理v-model
modelUpdater(node, value,key) {
//v-model是文本框的属性,给文本框赋值需要通过value属性
node.value = value;
new Watcher(this.vm, key, (newValue) => {
node.value = newValue;
});
}
update(node, key, attrName) {
//根据传递过来的属性名字拼接Updater后缀获取方法。
let updateFn = this[attrName + "Updater"];
updateFn && updateFn.call(this, node, this.vm[key], key); //注意:传递的是根据指令的值获取到的是data中对应属性的值。
}