对象参数修改监听实现【参考vue的observer】
我们要实现如下效果:
生成一个testObj的key为a的监听。
let testObj = {
a: 1
}
new Observer(testObj);
new Wathcher(testObj, 'a', () => {
console.log('我监听到你改变啦')
});
Observer
首先我们先生成一个Observer类,这个类用来遍历对象的key值,使用defineProperty来重写对象的get和set方法,从而实现监听.
基本结构如下所示:
- walk用来遍历所有key值
- defineReactive用用负责重写get set方法
class Observer {
constructor(obj) {
this.obj = obj;
this.walk(obj);
}
walk(obj) {
const keys = Object.keys(obj);
keys.map((key) => {
defineReactive(obj, key, obj[key]);
})
}
defineReactive(obj, key, value) {
const property = Object.getOwnPropertyDescriptor(obj, key);
if(!property.configurable) return;
const setter = property && property.set;
Object.defineProperty(obj, key,
get: function() {
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
// obj[key] = newVal; 这样赋值会出现递归调用的错误
}
}
});
}
}
可以看出,上述的代码只是重写了写get set方法,并没有实现事件监听。
事件监听的关键是观察者订阅者模式,我们简单实现一个事件订阅中心
Dep
简单实现的事件中心如下,只有两个方法,订阅和事件触发。
class Dep {
constructor() {
this.subs = [];
}
add(sub) {
this.subs.push(sub);
}
notify() {
this.subs.map((sub) => {
sub.update();
})
}
}
实现修改对象值,就能触发监听的函数,我们需要在defineReactive里注册一个事件中心,在set的时候,去通知事件中心,触发注册在该对象key下的订阅事件。
修改如下:...代表与之前一样
defineReactive(obj, key, value) {
...
const dep = new Dep();
Object.defineProperty(obj, key,
get: function() {
return val;
},
set: function(newVal) {
...
dep.notify();
}
});
}
那么问题来了,我们怎么将事件监听注册到对应的事件中心呢?我们可以利用重定义的get方法进行。
我们实现一下监听类
watcher
可以看出watcher拥有两个函数:
- update 用以执行监听函数
- get 用以触发重写后的get函数,来注册事件函数。
class Watcher {
constructor(obj, key, cb) {
this.obj = obj;
this.key = key;
this.cb = cb;
this.value = this.get();
}
update() {
this.cb && this.cb();
}
get() {
return this.obj[this.key];
}
}
但是存在两个个问题
- get函数怎么知道是普通取值获取的,还是在注册监听watcher时调用的呢
- get函数怎么拿到该watcher呢?
我们在Dep里定义一个唯一的静态变量target,用来存储当前watcher
Dep.target = null;
修改watcher中的get函数。可以看到在调用get之前,将当前watcher复制给了Dep.target,调用完对象的get方法后又把Dep.target置为了空。这样保证了不同watcher之间不会相互干扰。在调用对象get的时候,get里获取到的Dep.target一定是调用它的watcher
get() {
Dep.target = this;
const val = this.obj[this.key];
Dep.target = null;
return val;
}
同时,修改observer里的get方法.
Object.defineProperty(obj, key,
get: function() {
if(Dep.target) {
dep.add(Dep.target);
}
return val;
}
}
效果
执行下述代码,成功执行到监听函数啦
let testObj = {
a: 1
}
new Observer(testObj);
new Wathcher(testObj, 'a', () => {
console.log('我监听到你改变啦')
});
testObj.a = 12313;
问题
我们在实现Observer时,我们只重写了,对象最外层属性的get set. 深层的都没有处理。
完整代码如下:
class Dep {
constructor() {
this.subs = [];
}
add(sub) {
this.subs.push(sub);
}
notify() {
this.subs.map((sub) => {
sub.update();
})
}}
Dep.target = null;
class Wathcher {
constructor(obj, key, cb) {
this.obj = obj;
this.key = key;
this.cb = cb;
this.val = this.get();
}
get() {
Dep.target = this;
const newVal = this.obj[this.key];
Dep.target = null;
return newVal;
}
update() {
this.val = this.get();
this.cb && this.cb(this.val);
}}
// new Observer(obj)
class Observer { // 需要观察obj
constructor(obj) {
this.walk(obj);
}
walk(obj) {
const keys = Object.keys(obj);
keys.map((key) => {
this.defineReactive(obj, key, obj[key]);
})
}
observer(value) {
if(typeof value === 'object') {
return new Observer(value);
}
}
defineReactive(obj, key, val) {
const property = Object.getOwnPropertyDescriptor(obj, key);
if(!property.configurable) return;
const setter = property && property.set;
const depth = new Dep();
let childObj = this.observer(val);
const self = this;
Object.defineProperty(obj, key, {
get: function() {
// 说明是watcher来调用的
if(Dep.target) {
depth.add(Dep.target);
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
// obj[key] = newVal;
if (setter) {
setter.call(obj, newVal);
} else {
val = newVal;
// obj[key] = newVal; 为什么这样赋值就会出错
}
childObj = self.observer(newVal);
depth.notify();
}
});
}}
let testObj = {
a: {
b: 'fafa'
}}
new Observer(testObj, 'a');
new Wathcher(testObj, 'a', () => {
console.log('testObj.a 我监听到你改变啦:', testObj.a)
});
new Wathcher(testObj.a, 'b', () => {
console.log('testObj.a.b 我监听到你改变啦:', testObj.a.b)
});
testObj.a.b = 12331122;
testObj.a = 1