事前哔哔
上一节我们讲了响应式原理,比较笼统又难以理解,所以这一节我们就直接上手开始编写,在章节末尾我把observer对象转换成了js版本供你直接上手测试,你可以先copy边调试边理解接下来的编写流程
开始
observer的侦测是一个对象或者数组,所以一开始我们便要传递这个参数(取名为value)进来
class ObserverNext {
$value: any;
constructor(value) {
...
}
在前面响应式原理中我们说了我们要深度递归分析这个对象(就是data选项)内的数据所以我们还得有个walk方法
class ObserverNext {
$value: any;
constructor(value) {
...
this.walk(value)
}
private walk(obj: Object | Array<any>) {
for (const [key, val] of Object.entries(obj)) {
if (typeof val == "object") {
//同时判断数组和对象
new ObserverNext(key,val, obj);
}
}
}
现在就完成了深度递归,但是还没有分析的过程,所以我们得再写一个方法,名为detect来分析,这里我们用Proxy完成对该对象的监控(分析)
//observer中的detect私有方法
private detect(val: any, parent: any) {
const dep = this.dep//在constructor中定义
const proxy = new Proxy(val, {
get(obj, property) {
if (!obj.hasOwnProperty(property)) {
return;
}
dep.depend(property);
return obj[property];
},
set(obj, property, value) {
obj[property] = value;
dep.notify(property);
return true;
},
});
}
当对象被get了数据,我们就收集依赖,当对象set了数据,我们就通知依赖更新,
并且呢,这个proxy要被外部能够获取到,这里我们的解决方案是直接在父对象上替换
parent[key] = proxy;
所以我们一开始就要传递父对象(parent)和键(key)进来。
所以我们的constructor就变成了
constructor(key,value, parent) {
this.$key=key;
this.$value = value;
this.$parent = parent;
this.dep = new Dep();
this.walk(value);
this.detect(value, parent);
}
现在我们的observer变成了
class ObserverNext {
$value: any;
$parent: any;
$key:string
dep: any;
constructor(key,value, parent) {
this.$key=key;
this.$value = value;
this.$parent = parent;
this.dep = new Dep();
//def(value, "__ob__", this);
this.walk(value);
this.detect(value, parent);
}
private walk(obj: Object | Array<any>) {
for (const [key, val] of Object.entries(obj)) {
if (typeof val == "object") {
//同时判断数组和对象
new ObserverNext(key,val, obj);
}
}
}
private detect(val: any, parent: any) {
const dep = this.dep
const key=this.$key
const proxy = new Proxy(val, {
get(obj, property) {
if (!obj.hasOwnProperty(property)) {
return;
}
dep.depend(property);
return obj[property];
},
set(obj, property, value) {
obj[property] = value;
dep.notify(property);
//if(parent.__ob__)parent.__ob__.dep.notify(key)
return true;
},
});
parent[key] = proxy;
}
}
但是现在有个bug,如果儿子对象更新了,它只会通知自己的依赖收集器(dep)更新,父对象不会感知到任何异常!
所以我们得通知父对象的观察者类实例(ObserverNext)更新
因此,在每个对象中我们都要记录一下观察者实例
大概如下:
constructor(key,value,parent){
...
def(value, "__ob__", this);
//相当于value.__ob__=this;
...
}
然后在proxy的set中,我们要通知父对象更新于是
set(){
if(parent.__ob__)parent.__ob__.dep.notify(key)
}
最终成果
我们原版是typescript版本的,但为了方便各位直接丢控制台里测试,我转换成了js版本以供各位调试
class Dep {
constructor() {}
depend() {
console.log("依赖收集");
}
notify() {
console.log("依赖更新");
}
}
function def(obj, key, val, enumerable=false) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true,
});
}
class ObserverNext {
constructor(key, value, parent) {
this.$key = key;
this.$value = value;
this.$parent = parent;
this.dep = new Dep();
def(value, "__ob__", this);
this.walk(value);
this.detect(value, parent);
}
walk(obj) {
for (const [key, val] of Object.entries(obj)) {
if (typeof val == "object") {
//同时判断数组和对象
new ObserverNext(key, val, obj);
}
}
}
detect(val, parent) {
const dep = this.dep;
const key = this.$key;
const proxy = new Proxy(val, {
get(obj, property) {
if (!obj.hasOwnProperty(property)) {
return;
}
dep.depend(property);
return obj[property];
},
set(obj, property, value) {
obj[property] = value;
dep.notify(property);
if (parent.__ob__) parent.__ob__.dep.notify(key);
return true;
},
});
parent[key] = proxy;
}
}
const vm = {
data: {
attr1: {
a: 1,
b: 2,
c: 3,
},
array: [1, 2, 3],
},
};
new ObserverNext('data',vm.data,vm);
//测试
//vm.data.attr1 ->return 依赖收集
归档
# 手摸手教你实现一个简单vue(1)响应式原理
# 手摸手教你实现一个简单vue(2)上手编写observer
# 手摸手教你实现一个简单vue(3)上手编写dep和watcher