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这三者的关系。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容