简单实现vue2.0中的数据监听,大概过程如下
1.递归遍历监听对象,创建Observer通过defineproperty劫持属性,并给每个属性创建一个消息管理器(dep)
2.当监听某个属性时,会创建Watcher作为订阅者注册到该属性创建的消息管理器(dep中)
3.当监听对象属性发生改变时,该属性的消息管理器(dep)就会通知到订阅的Watcher来指定回调
代码和注释如下,主要魅力在Watcher的get方法这里
//判断是否是对象 是对象就通过Observer进行对象属性劫持
function observe(data) {
//只有对象才需要劫持
if (!data || typeof data !== "object") return;
return new Observer(data);
}
//Observer类 作用就是通过defineProperty来劫持所有对象属性
class Observer {
constructor(obj) {
this.obj = obj;
this.walk(obj);
}
walk(obj) {
Object.keys(obj).forEach((key) => {
this.convert(key, obj[key]);
});
}
convert(key, val) {
defineReactive(this.obj, key, val);
}
}
function defineReactive(obj, key, val) {
//每个属性都需要创建一个消息管理器dep
const dep = new Dep();
//继续劫持属性值
let childOb = observe(val);
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
get: () => {
//该属性被监听时,会实例化一个watcher
//实例化过程中会将实例赋值给Dep.target然后获取监听的属性值
//这时候会进入到这里调用dep.depend来将watcher加入到消息管理器dep的订阅中
if (Dep.target) {
dep.depend();
}
return val;
},
set: (newVal) => {
if (newVal === val) return;
val = newVal;
//数据改变时,劫持新的属性值
childOb = observe(val);
//然后通过消息管理器dep通知所有的订阅者
dep.notify();
},
});
}
//Dep是消息管理器,负责保存订阅属性的watcher和当属性值变化时通知订阅者更新
let uid = 0;
class Dep {
constructor() {
//唯一id
this.id = uid++;
//记录订阅者
this.subs = [];
}
depend() {
//这里将是否需要添加订阅的逻辑交给watcher来判断,避免重复添加同一个watcher
Dep.target.addDep(this);
}
addSub(sub) {
//添加订阅
this.subs.push(sub);
}
notify() {
//向订阅者发送更新消息
this.subs.forEach((sub) => sub.update());
}
}
//watcher 订阅者 负责更新
class Watcher {
constructor(data, prop, cb) {
//记录dep 用于避免重复添加到同一dep
this.depIds = {};
this.data = data;
this.prop = prop;
this.cb = cb;
//这里的get一是记录属性值,二是将watcher实例赋值给Dep.target,
//然后获取属性值进入属性的get方法来使watcher订阅该属性的消息管理器
this.val = this.get();
}
addDep(dep) {
//如果depIds中已经记录来dep.id说明该watcher已经添加到该dep中了
if (!this.depIds.hasOwnProperty(dep.id)) {
this.depIds[dep.id] = dep;
dep.addSub(this);
}
}
//更新操作
update() {
this.run();
}
//执行更新回调
run() {
let val = this.get();
if (val !== this.val) {
this.val = val;
this.cb(this.data, val);
}
}
get() {
//实例赋值给Dep.target
Dep.target = this;
//获取属性值进入到get方法 调用dep.depend
let val = this.data[this.prop];
//还原
Dep.target = null;
return val;
}
}
//监听对象属性方法
function watch(obj, prop, cb) {
new Watcher(obj, prop, cb);
}
var obj = {
name: "jay",
age: 28,
watch(prop, cb) {
new Watcher(this, prop, cb);
},
};
observe(obj);
obj.watch("name", function (obj, val) {
console.log(val + "!!@#$");
});
obj.name = "xu";
网友评论