mobx(5.14.0)源码解读一 observable

693 阅读4分钟

前言

mobx源码解读系列文章,主要包含observable、autorun、observable属性更新如何触发autorun回调三个部分,在这三篇文章中会带着读者通过一步一步debug的方式,来理解mobx的基本原理,如有不妥之处,还望指正。

Tips:

(1)这篇文章是前提,所以一定要对这篇文章生成的数据结构有一定的印象,后续对autorun及observable属性更新如何触发autorun回调两篇文章的解读才能足够清晰明了。

(2)系列文章中提到的$mobx就是截图中的Symbol("mobx administration"); 为了书写方便,直接使用$mobx代替。对Symbol类型不是很了解的,可以参考这里

准备工作

下载并运行demo

开始解读

为了减少篇幅使流程更加清晰,贴出来的代码会有所删减,只保留主逻辑,还望谅解~~~

import { observable, autorun } from 'mobx'
const addBtn = document.getElementById('add')
const pointsLabel = document.getElementById('pointsLabel')
const vipMember = observable({
    name: 'Susan',
    age: 18,
    points: 3,
});

console.log(pointsLabel)

autorun(() => {
  pointsLabel.innerText = `[${vipMember.name}] has [${vipMember.points}] points`
})

addBtn.addEventListener('click', ()=> {
  vipMember.points ++
})

demo中主要使用了mobx提供的observable、autorun两个方法,通过点击【+】按钮,更新points,从而更新页面显示内容。

首先我们从observable入手先来了解一下,这个方法会将对象封装成什么样的值赋给vipMember.

Chrome打开调试工具,找到node_modules/mobx/lib/mobx.module.js文件中的var observable = createObservable;,即observable的定义。找到createObservable方法

Code-1

function createObservable(v, arg2, arg3) {
    // @observable someProp;
    if (typeof arguments[1] === "string") {
        return deepDecorator.apply(null, arguments);
    }
    // it is an observable already, done
    if (isObservable(v))
        return v;
    // something that can be converted and mutated?
    var res = isPlainObject(v)
        ? observable.object(v, arg2, arg3)
        : Array.isArray(v)
            ? observable.array(v, arg2)
            : isES6Map(v)
                ? observable.map(v, arg2)
                : isES6Set(v)
                    ? observable.set(v, arg2)
                    : v;
    // this value could be converted to a new observable data structure, return it
    if (res !== v) return res;
}

(1)判断arg2是否为string,demo没传,所以跳过

(2)判断是否是可观察对象,由于目前啥都还没干,所以跳过

(3)isPlainObject判断传入参数是否为object,demo是以object为例的,从此展开

(4)调用observable.object()方法,往下追踪observable.object方法,找到observableFactories里定义了object成员方法,然后还有一句Object.keys(observableFactories).forEach(function (name) { return (observable[name] = observableFactories[name]); }); 所以observable.object即observableFactories.object方法.

Code-2

object: function (props, decorators, options) {
        if (typeof arguments[1] === "string")
            incorrectlyUsedAsDecorator("object");
        var o = asCreateObservableOptions(options);
        if (o.proxy === false) {
            return extendObservable({}, props, decorators, o);
        }
        else {
            var defaultDecorator = getDefaultDecoratorFromObjectOptions(o);
            var base = extendObservable({}, undefined, undefined, o);
            var proxy = createDynamicObservableObject(base);
            extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);
            return proxy;
        }
    }
  1. 参数decorators和options都为undefined,其中var o = asCreateObservableOptions(options);追踪到
o = {
    deep: true,
    name: undefined,
    defaultDecorator: undefined,
    proxy: true
}

所以o.proxy会等于true, 走到else逻辑,else逻辑分2、3、4、5点进行详细的介绍

  1. var defaultDecorator = getDefaultDecoratorFromObjectOptions(o);

调用栈: getDefaultDecoratorFromObjectOptions -> deepDecorator -> createDecoratorForEnhancer

接下来看看createDecoratorForEnhancer方法干了什么?

function createDecoratorForEnhancer(enhancer) {
    var decorator = createPropDecorator(true, function (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) {
        ...
        asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer);
    }
    // 三目运算符之后判断,代码有修改
    var res = decorator;
    res.enhancer = enhancer;
    return res;
}

createDecoratorForEnhancer返回一个函数createPropDecorator,这里暂且不做介绍,我们会在介绍extendObservableObjectWithProperties的时候再展开。

  1. var base = extendObservable({}, undefined, undefined, o);

Code-2-3-1

function extendObservable(target, properties, decorators, options) {
    ...
    options = asCreateObservableOptions(options);
    var defaultDecorator = getDefaultDecoratorFromObjectOptions(options);
    initializeInstance(target);
    asObservableObject(target, options.name, defaultDecorator.enhancer);
    if (properties)
        extendObservableObjectWithProperties(target, properties, decorators, defaultDecorator);
    return target;
}

asCreateObservableOptionsgetDefaultDecoratorFromObjectOptions前面都已经介绍过了,这里就不做赘述,重点来看一下asObservableObject方法

Code-2-3-2

function asObservableObject(target, name, defaultEnhancer) {
    ...
    if (Object.prototype.hasOwnProperty.call(target, $mobx))
        return target[$mobx];
    ...
    var adm = new ObservableObjectAdministration(target, new Map(), stringifyKey(name), defaultEnhancer);
    addHiddenProp(target, $mobx, adm);
    return adm
}

(1)判断传入对象是否存在$mobx属性,如果存在直接将$mobx属性返回,由于这里传入的是一个空对象{},所以走下面的逻辑

(2)创建一个ObservableObjectAdministration,简称 adm,将其添加到$mobx属性。

总之,base这时候就是一个拥有$mobx属性的对象,其中$mobx为ObservableObjectAdministration的一个实例。

  1. var proxy = createDynamicObservableObject(base);
function createDynamicObservableObject(base) {
    var proxy = new Proxy(base, objectProxyTraps);
    base[$mobx].proxy = proxy;
    return proxy;
}

这里对Proxy不是很熟悉的同学,可以参考这里

(1) objectProxyTraps定制了对base作读写操作时的拦截行为,将会在mobx(5.14.0)源码解读二 autorunmobx(5.14.0)源码解读三 observable更新如何触发autorun回调文章中做详细介绍。

(2) 为$mobx属性添加proxy属性

**总之, proxy是一个具有拦截行为的Proxy对象,target为第3点返回的包含$mobx属性的base, 这里只是实例化了一个proxy对象,该对象中除了$mobx,还什么都没有,真正起作用的在第5点

  1. extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator);
function extendObservableObjectWithProperties(target, properties, decorators, defaultDecorator) {
    ...
    var keys = getPlainObjectKeys(properties);
    for (var keys_2 = __values(keys), keys_2_1 = keys_2.next(); !keys_2_1.done; keys_2_1 = keys_2.next()) {
         var key = keys_2_1.value;
         // 这里是多个三目运算符组合,最终会是defaultDecorator,
         var decorator = defaultDecorator;
         var resultDescriptor = decorator(target, key, descriptor, true);
    }        
}

(1)遍历循环属性值,去调用第2点中提到的defaultDecorator方法,这时候,我们回过头去看当时在defaultDecorator返回值中createPropDecorator做了什么

var decorator = createPropDecorator(true, function (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) {
        var initialValue = ?
        asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer);
    });
function createPropDecorator(propertyInitiallyEnumerable, propertyCreator) {
    return function decoratorFactory() {
        var decoratorArguments;
        var decorator = function decorate(target, prop, descriptor, applyImmediately
        ) {
            if (applyImmediately === true) {
                propertyCreator(target, prop, descriptor, target, decoratorArguments);
                return null;
            }
        }
        return decorator
    }
}

调用栈:decorator(target, key, descriptor, true) -> defaultDecorator -> createPropDecorator -> decoratorFactory里的decorator方法, 所以传入的applyImmediately为true,进入if逻辑,直接调用propertyCreator,即createPropDecorator方法的第二个参数

function (target, propertyName, descriptor, _decoratorTarget, decoratorArgs) {
    var initialValue = ?
    asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer);
});

asObservableObject在Code-2-3-2已经介绍过了,之前target是个空对象,而这时候是一个包含$mobx属性的对象,所以走逻辑if逻辑,返回属性$mobx(ObservableObjectAdministration的一个实例),然后调用ObservableObjectAdministration.prototype.addObservableProp方法

ObservableObjectAdministration.prototype.addObservableProp = function (propName, newValue, enhancer) {
    ...
    var observable = new ObservableValue(newValue, enhancer, this.name + "." + stringifyKey(propName), false);
    this.values.set(propName, observable);
    newValue = observable.value;
    Object.defineProperty(target, propName, generateObservablePropConfig(propName));
    ...
}

(1)key为属性名,值为ObservableValue实例,将其set给$mobx的values属性,其中类ObservableValue包含set、get、setNewValue等方法,将会在mobx(5.14.0)源码解读二 autorunmobx(5.14.0)源码解读三 observable更新如何触发autorun回调中介绍,这里不做赘述。

(2)为$mobx添加key为属性名,值为generateObservablePropConfig(propName)的属性

总之extendObservableObjectWithProperties就是属性值,包装成ObservableValue类型,存储在$mobx.values中,同时将传入的属性添加到$mobx中

  1. return proxy 最终vipMember被包装成如下所示的结构

注意,这时候绿框内的observers是空的,这个将会在autorun的时候进行依赖收集。

总结

observable主要做了以下几件事:

  1. 创建一个空对象,并设置默认属性$mobx(即Symbol("mobx administration"))
  2. 将该对象转化成一个Proxy实例,从此对该对象的属性进行get、set操作都将被拦截
  3. 遍历传入对象(即observable的参数)的属性,将其添加到之前创建的空对象中,同时将属性值转化成ObservableValue实例,set到$mobx.values中。
  4. 返回Proxy实例,即原来的原始对象vipMember现在已经被替换成了一个包含原有属性及$mobx属性的对象。

其他文章

mobx(5.14.0)源码解读二 autorun

mobx(5.14.0)源码解读三 observable属性自更新如何触发autorun回调