本文将初步讲解mobx的原理,用代码模拟实现observable、observer、autorun这三个常见函数。
首先,介绍一下一个最核心的管理类dependenceManager,它记录了一个全局的Map,每个Map都有唯一一个ID与之对应,每项表示当一个被observable装饰过的属性值发生set行为时,其所对应的监听函数数组。
let nowObserver = null;
let nowTarget = null;
let observerStack = [];
let targetStack = [];
let isCollecting = false;
class ObserverManages {
constructor() {
this._observers = {};
}
_addNowObserver = (proxyID) => {
this._observers[proxyID] = this._observers[proxyID] || {};
this._observers[proxyID].target = nowTarget;
this._observers[proxyID].watchers = this._observers[proxyID].watchers || [];
this._observers[proxyID].watchers.push(nowObserver);
};
trigger = (id) => {
let ds = this._observers[id];
if (ds && ds.watchers) {
ds.watchers.forEach(d => {
d.call(ds.target || this);
});
}
};
beginCollect = (observer, target) => {
isCollecting = true;
observerStack.push(observer);
targetStack.push(target);
nowObserver = observerStack.length > 0 ? observerStack[observerStack.length - 1] : null;
nowTarget = targetStack.length > 0 ? targetStack[targetStack.length - 1] : null;
};
collect = (proxyID) => {
if (nowObserver) {
this._addNowObserver(proxyID);
}
};
endCollect = () => {
isCollecting = false;
observerStack.pop();
targetStack.pop();
nowObserver = observerStack.length > 0 ? observerStack[observerStack.length - 1] : null;
nowTarget = targetStack.length > 0 ? targetStack[targetStack.length - 1] : null;
};
}
export default new ObserverManages();
- 定义了5个变量,nowObserver和nowTarget表示需要被添加的最新的监听函数以及所对应的context
- 构造函数定义了一个空对象,里面存放了所有的映射关系
- beginCollect函数用于刷新最新的监听数组,endCollect函数与之相反
- collect函数用于添加一个新的监听(所要监听的对象已在beginCollect中完成)
- trigger函数用于执行对应的监听函数数组
接下来看看observable是如何工作的:
import observerManagers from './s-observer-manager';
let obIDCounter = 1;
export default class Observable {
obID = 0;
value = null;
constructor(v) {
this.obID = 'ob-' + (++obIDCounter);
if (Array.isArray(v)) {
this._wrapArrayProxy(v);
} else {
this.value = v;
}
}
get = () => {
observerManagers.collect(this.obID);
return this.value;
};
set = (v) => {
if (Array.isArray(v)) {
this._wrapArrayProxy(v);
} else {
this.value = v;
}
observerManagers.trigger(this.obID);
};
trigger = () => {
observerManagers.trigger(this.obID);
};
/**
* 对数组包装Proxy拦截数组操作的动作
* @param v
* @private
*/
_wrapArrayProxy = (v) => {
this.value = new Proxy(v, {
set: (obj, key, value) => {
obj[key] = value;
if (key != 'length') {
this.trigger();
}
return true;
}
});
};
}
最主要的就是get和set方法,它们每当对象进行取值或者赋值操作时,都会自动出发,可以看到当发生get行为时就会调用observerManagers的collect来进行数据收集,当发生set行为时会调用obserManagers.trigger函数来执行其对应的所有监听函数。
当我们使用mobx的autorun函数时,如果使用了observable修饰的变量,每当该变量变化时,它都会自动出发该函数,那么是怎么实现的呢?其实很简单:
import observerManagers from './s-observer-manager';
const autorun = function (handler) {
observerManagers.beginCollect(handler);
handler();
observerManagers.endCollect();
};
可以看到,我们的autorun只有三句话,开始收集、首次执行监听函数、结束收集。那么它又是如何与observable产生互动的呢?上面说过,当observerManages开始收集时,它会刷新最新的监听函数,此处将handler传入后,handler变成了最新的监听函数,然后会调用handler方法,handler方法中会触发被observable修饰过的变量的get行为,从而会导致observerManagers.collect方法的触发,于是会将此次值的变化与相应的监听函数绑定起来。等到该变量触发set行为时,便会调用相应的监听函数数组。
接下来看看如何对observable进行封装:
import Observable from './s-observable';
let createObservableProperty = function (target, property) {
let observable = new Observable(target[property]);
Object.defineProperty(target, property, {
get: function () {
return observable.get();
},
set: function (value) {
return observable.set(value);
}
});
if (typeof (target[property]) === 'object') {
for (let i in target[property]) {
if (target[property].hasOwnProperty(i)) {
createObservableProperty(target[property], i);
}
}
}
};
let extendsObservable = function (target, obj) {
for (let i in obj) {
if (obj.hasOwnProperty(i)) {
target[i] = obj[i];
createObservableProperty(target, i);
}
}
};
let createObservable = function (target) {
for (let i in target) {
if (target.hasOwnProperty(i)) {
createObservableProperty(target, i);
}
}
};
export {
extendsObservable,
createObservable
}
可以看到,我们导出了两个extendObservable和createObservable两个函数,它们所做的很简单,就是将自身的每个属性都转换为observable状态。这样做的好处在于,假设当autorun依赖于name.key.key,只有当name.key.key变化时才会出发autorun,而name或者name.key变化都不会触发autorun。
最后,来看看我们的装饰器:
import Observable from './s-observable';
import autorun from './s-autorun';
import {createObservable} from './s-extendObservable';
function observable(target, name, descriptor) {
let v = descriptor.initializer.call(this);
// 如果值是对象,为其值也创建observable
if (typeof v === 'object') {
createObservable(v);
}
let observable = new Observable(v);
return {
enumerable: true,
configurable: true,
get: function () {
return observable.get();
},
set: function (v) {
// 重新赋值对象的时候,为其值也创建observable
if (typeof v === 'object') {
createObservable(v);
}
return observable.set(v);
}
};
}
let ReactMixin = {
componentWillMount: function () {
autorun(() => {
this.render();
this.forceUpdate();
});
}
};
function observer(target) {
const targetCWM = target.prototype.componentWillMount;
target.prototype.componentWillMount = function () {
targetCWM && targetCWM.call(this);
ReactMixin.componentWillMount.call(this);
}
}
export {
observable,
observer
}
这里要说的是observer函数,它重写了原组件的componentWillMount对象,其目的是为了给render函数加上autorun监听。
总结:这个过程其实并不复杂,关键是要理清observable、autorun和dependenceManager这三者的关系。