前言
vue2 在开发的时候,es6 并未完全普及,因此 vue2 对对象的属性的变化监听采用了 es5 的 Object.defineProperty
方法来实现。但是由于 Object.defineProperty
的局限性,只能对对象已有的属性进行改造成 get / set
实现监听,无法监听到对象的属性新增和属性删除操作。
在对象属性的 get 操作时进行依赖收集,在对象属性的 set 操作时进行依赖的触发。收集的依赖其实就是 Watcher 的实例。而且对象收集和建立 Watcher 二者之间本来毫无关系。是巧妙地让 Watcher 实例化的时候获取了值,触发 get 操作。get 就会知道要收集依赖了。但是这个依赖也就是 Watcher 实例是无法通过 get 的时候传递的,因此,vue 就在 Watcher 实例化的时候将自己放到了一个约定的地方,即window.target
处,在触发 get 操作时会去该地方取依赖,即可完成依赖的收集。
实现
class Dep {
constructor() {
// subs 是依赖,依赖就是 Watcher 的实例
this.subs = []
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
const index = this.subs.indexOf(sub);
if(index > -1) {
this.subs.splice(index, 1);
}
}
depend() {
if(window.target) {
this.addSub(window.target);
}
}
notify() {
// 遍历通知全部依赖
const subs = this.subs;
for(let i = 0; i < subs.length; i++) {
subs[i].update();
}
}
}
function defineReactive(data, key, val) {
// 对象嵌套时,递归子属性
if(typeof val === "object") {
new Observer(val);
}
let dep = new Dep();
Object.defineProperty(data, key, {
enumerable: true,
configurable: true,
get: function() {
// 收集依赖
dep.depend();
return val;
},
set: function (newVal) {
if(newVal === val) return;
val = newVal;
// 通知依赖
dep.notify();
}
})
}
class Watcher {
constructor(data, exp, cb) {
this.data = data;
// 执行 this.getter 就可以从 data 读取值
this.getter = this.parsePath(exp);
this.cb = cb;
// 获取依赖的实际值
this.value = this.get();
}
get() {
window.target = this;
let value = this.getter(this.data);
window.target = undefined;
return value;
}
update() {
const oldVal = this.value;
// 获取新的值,不会再收集一遍依赖,因为没有改变 window.target
this.value = this.getter(this.data);
this.cb(this.value, oldVal);
}
parsePath(path) {
const regex = /[^\w.&]/
if(regex.test(path)) return;
const keys = path.split(".");
return function(obj) {
for(let i = 0; i < keys.length; i++) {
if(!obj) return;
obj = obj[keys[i]];
}
return obj;
}
}
}
class Observer {
constructor(data) {
this.data = data;
if(!Array.isArray(this.data)) {
this.walk(this.data);
}
}
walk(obj) {
const keys = Object.keys(obj);
for(let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i], obj[keys[i]]);
}
}
}
使用上述的变化侦测。
const data = {
a: {
b: 123,
c: 456
}
}
new Observer(data);
new Watcher(data, 'a.b', (val, oldVal) => {
console.log(`监听到 a.b 变化了,变化后的值为 ${val},旧的值为 ${oldVal}`);
})
setTimeout(() => {
console.log("修改数据")
data.a.b = 789;
}, 3000)