Mobx原理初解析

本文将初步讲解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();
  1. 定义了5个变量,nowObserver和nowTarget表示需要被添加的最新的监听函数以及所对应的context
  2. 构造函数定义了一个空对象,里面存放了所有的映射关系
  3. beginCollect函数用于刷新最新的监听数组,endCollect函数与之相反
  4. collect函数用于添加一个新的监听(所要监听的对象已在beginCollect中完成)
  5. 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这三者的关系。

推荐阅读更多精彩内容