Mini Data Observer
响应性的实现
Angular:脏值检测
Vue:依赖跟踪
1. A => B
B = A * 10,B依赖A,B的响应性的实现方法,是在A的set方法中传入一个改变B的方法。(这个方法解释了B对A的依赖)
let a = 1;
let b = a * 10;
function setA(v) {
a = v;
updateB();
}
function updateB() {
b = a * 10;
}
setA(2);
console.log(b);//20
2.监听A的变更
ES5的Object.defineProperty提供监听属性变更的功能(指set方法)
const state = {
a: 0
};
let b;
function updateB() {
b = state.a * 10;
}
let internalValue = state.a;
Object.defineProperty(state, "a", {
get() {
return internalValue;
},
set(v) {
internalValue = v;
updateB();
}
})
state.a = 2;
console.log(b);//20
3.收集依赖
前面都是假定依赖A的变量只有B,且这是已知的。
而实际业务中,依赖A的变量随着代码的编写会逐渐增多,这就要求系统能够动态维护所有依赖A的变量对于A的依赖关系,以便在A发生变更时按照每个依赖关系修改每个依赖项。
需要实现一个依赖跟踪类Dep
,类里有一个叫depend
方法,该方法用于收集依赖项;另外还有一个notify
方法,该方法用于触发依赖项的执行,也就是说只要在之前使用depend
方法收集的依赖项,当调用notfiy
方法时会被触发执行。
window.Dep = class Dep {
constructor () {
// 存放依赖关系
this.subscribers = new Set()
}
// 新增依赖关系
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
// 所有依赖项,按照依赖关系执行更新操作
notify () {
this.subscribers.forEach(sub => sub())
}
}
let activeUpdate = null
//新增依赖关系(后面详细解释)
function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
4.合并2、3步
①.实现
class Dep {
constructor () {
this.subscribers = new Set()
}
depend () {
if (activeUpdate) {
this.subscribers.add(activeUpdate)
}
}
notify () {
this.subscribers.forEach(sub => sub())
}
}
function observe (obj) {
Object.keys(obj).forEach(key => {
let internalValue = obj[key]
const dep = new Dep()
Object.defineProperty(obj, key, {
get () {
dep.depend()
return internalValue
},
set (newVal) {
const changed = internalValue !== newVal
internalValue = newVal
if (changed) {
dep.notify()
}
}
})
})
return obj
}
let activeUpdate = null
function autorun (update) {
const wrappedUpdate = () => {
activeUpdate = wrappedUpdate
update()
activeUpdate = null
}
wrappedUpdate()
}
②.使用
//情景模拟
{
const state = {
count: 1
};
observe(state);
let foo = 1;
let fire = 1;
autorun(() => {
foo = state.count * 10;
console.log("state.count is " + state.count);
console.log("foo is " + foo);
console.log("fire is " + fire);
});
/**
* ①控制台打印:
* state.count is 1
* foo is 10
* fire is 1
*/
state.count++;
/**
* ②控制台打印:
* state.count is 2
* foo is 20
* fire is 1
*/
autorun(() => {
fire = state.count + 100;
console.log("state.count is " + state.count);
console.log("foo is " + foo);
console.log("fire is " + fire);
});
/**
* ③控制台打印:
* state.count is 2
* foo is 20
* fire is 102
*/
state.count++;
/**
* ③控制台打印:
* state.count is 3
* foo is 30
* fire is 102
* state.count is 3
* foo is 30
* fire is 103
*/
}
autorun这个方法的参数是一个function,这个function里面执行了updata方法,而update方法是按照依赖关系更新依赖项,这一操作势必会访问A,既然访问A,势必调用getter,既然调用getter,势必调用dep.depend
来新增依赖关系。所以autorun这个方法是用来新增依赖关系的。
③.补充
上面说到”update方法是按照依赖关系更新依赖项,这一操作势必会访问A,既然访问A,势必调用getter”,接下来验证一下:
const state = {
count: 0
}
let value = state.count;
Object.defineProperty(state, "count", {
get() {
console.log(value);
return value;
}
})
let foo = 0;
// 执行下面这一句
// 会执行get方法打印state.count的值
// 即打印 0
foo = state.count + 1;