export default class Watcher{
constructor(vm, expOrFn, cb) {
this.vm = vm;
//执行this.getter(),此处可以读取data.a.b.c的内容
this.getter = parsePath(expOrFn);
this.cb = cb;
this.value = this.get();
}
get(){
window.target = this;
let value = this.getter.call(this.vm, this,vm);
window.target = undefined;
return value;
}
update(){
const oldValue = this.value;
this.value = this.get();
this.cb.call(this.vm, this.value, oldValue);
}
}
在get方法中先把window.target设置成了this,也就是当前的watcher实例,然后再读一下data.a.b.c的值,这会触发getter。
触发getter就会触发收集依赖的逻辑。而关于收集依赖,会从window.target中读取一个依赖并添加到Dep中。
这就导致,只要先在window.target附一个this,然后再读一下值去触发getter就可以把this主动添加到keypath的Dep中。
依赖注入到Dep中后,每当data.a.b.c的值发生变化时,就会让依赖列表中所有的依赖循环触发update方法,也就是watcher中的update方法。而update方法会执行参数中的回调函数,将value和oldValue传到参数中。
所以,其实不管是用户执行的vm.$watch(‘a.b.c’, (value, oldValue) => {}),还是模板中用到的data,都是通过watcher来通知自己是否需要发生变化。
那么parsePath是怎么读取一个字符串的keypath的,下面来解释一下:
const bailRE = /[^\w.$]/;
export function parsePath(path){
if(bailRE.test(path)){
return;
}
const segments = path.split(".")
**真题解析、进阶学习笔记、最新讲解视频、实战项目源码、学习路线大纲**
**详情关注公中号【编程进阶路】**
return function (obj){
for(let i = 0; i < segments.length; i++){
if(!obj){
return;
}
obj = obj[segments[i]];
}
return obj;
}
}
可以看到,先将keypath用.分割成数组,然后循环数组一层一层去读数据,最后拿到的obj就是keyPath中想要读的数据。
2.递归侦测所有key
现在,其实已经可以实现变化侦测的功能了,但是前面介绍的代码只能侦测数据中的某一个属性,我们希望把数据中的所有属性(包括子属性)都侦测到,所以要封装一个Observer类。这个类的作用是将一个数据内的所有属性(包括子属性)都转换成getter/setter的形式,然后去追踪它们的变化:
export class Observer{
constructor(value) {
this.value = value;
if (!Array.isArray(value)){
this.walk(value)
}
}
walk(obj){
const keys = Object.keys(obj);
for (let i = 0; i < keys.length; i++){
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
function defineReactive(data, key, val) {
if (typeof val == 'object'){
new Object(val);
}
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function(){
dep.depend();
return val;
},
set: function(newVal){
if (val == newVal){
return;
}
val = newVal;
dep.notify();
}
})
}
在上面的代码中,我们定义了Observer类,它用来将一个正常的object转换成被侦测的object。
然后判断数据的类型,只有object类型的数据才会调用walk将每一个属性转换成getter/setter的形式来侦测变化。
最后,在defineReavtive中新增new Observer(val)来递归子属性,这样我们就可以把data中的所有属性(包括子属性)都转换成getter/setter 来侦测变化。
3.关于Object的问题
前面介绍了Object类型数据的变化侦测原理,了解了数据的变化是通过getter/setter来追踪的。也正是由于这种追踪方式,有些语法中即便是数据发生了变化,vue.is也追踪不到。
比如,向Object添加属性:
var vm = new Vue({
el: "#el",
template: "#demo-tenplate",
methods: {
action(){
this.obj.name = "berwin";
### 总结
阿里十分注重你对源码的理解,对你所学,所用东西的理解,对项目的理解。


