Mobx 源码解析

前言

在Git 找到Mobx 的源码, 发现其是使用TypeScript 编写,由于我对Typescrit 没有项目经验,因此我先会将其编译成JavaScript,因此咱们能够运行以下脚本或者从CDN直接下载一份编译过的源码,咱们能够选择umd 规范脚本:html

  1. git clone git@github.com:mobxjs/mobx.git
  2. npm i
  3. npm run quick-build

我直接从CDN 下载了一份源码, 而后进行分析。git

Demo

首先咱们从一个最基本的Demo开始,来看Mobx 的基本使用方式:github

const addBtn = document.getElementById('add')
const minusBtn = document.getElementById('minus')
const incomeLabel = document.getElementById('incomeLabel')
const bankUser = mobx.observable({
    name: 'Ivan Fan',
    income: 3,
    debit: 2
});

const incomeDisposer = mobx.autorun(() => {
    incomeLabel.innerText = `Ivan Fan income is ${bankUser.income}`
})

addBtn.addEventListener('click', ()=> {
    bankUser.income ++
})
minusBtn.addEventListener('click', () => {
    bankUser.income --
})

复制代码复制代码

咱们的界面很是简单,如图: ajax


图1
两个Button , 一个label. 咱们在js 文件中,咱们给两个按钮添加了**click** 事件,事件的主体很是简单`bankUser.income ++` `bankUser.income --`, 就是对`bankuser` 的`income` 属性进行了自增或者自减,很是神奇, 当咱们点击对应的按钮的时候, 中间的label 的内容发生了变化。可是咱们在Button 的点击事件中并无去操做**incomeLabel** 的内容,可是其内容确实随着点击事件,实时发生了变化。究其缘由,只有如下代码对**incomeLabel** 的text 进行了处理: ``` const incomeDisposer = mobx.autorun(() => { incomeLabel.innerText = `Ivan Fan income is ${bankUser.income}` }) ``` 这就是**Mobx** 的最简单神秘的功能,咱们能够先今后开始深刻研究它。

observable

从上面的JS文件中,咱们发现其中引用了mobx 两个方法,分别是npm

autorun,是的,是这样两个方法,让 incomeLabel 在点击按钮的时候实时的发生了变化,因此咱们接下来会对这两个方法进行深刻分析,这一章节咱们会先分析
observable
先进行分析。 咱们先打开Mobx的 源码, 若是咱们用 Vscode 打开这个源码,咱们能够用快捷键 Ctrl + K Ctrl + 0 将代码都折叠起来, 而后在打开, 找到 exports 的代码块,咱们能够查看mobx 都暴露出了哪些方法:


图2
暴露了一些列方法,咱们后续会使用。

observable,翻译成中文就是能够观测的, 咱们如今来调试这个方法, 咱们能够const bankUser = mobx.observable({ 这一行打一个断点,而后F11,跳进去,发现源码对应的是一个bash

createObservable
方法,也就是建立一个能够观察的对象:

var observable$$1 = createObservable;
function createObservable(v, arg2, arg3) {
    if (typeof arguments[1] === "string") {
        return deepDecorator$$1.apply(null, arguments);
    }
    if (isObservable$$1(v))
        return v;
    var res = isPlainObject$$1(v)
        ? observable$$1.object(v, arg2, arg3)
        : Array.isArray(v)
            ? observable$$1.array(v, arg2)
            : isES6Map$$1(v)
                ? observable$$1.map(v, arg2)
                : v;
    if (res !== v)
        return res;
    // otherwise, just box it
    fail$$1(process.env.NODE_ENV !== "production" &&
        "The provided value could not be converted into an observable. If you want just create an observable reference to the object use 'observable.box(value)'");
}
复制代码复制代码

上面代码很简单,参数有三个,可是咱们在调用的时候,值传递了一个参数, 因此咱们暂且只要关心第一个参数app

r
.如下是这个functin 的基本逻辑:

  1. 若是传入的第二个参数是一个字符串, 则直接调用deepDecorator$$1.apply(null, arguments);
  2. 判断第一个参数是否已是一个可观察的对象了,若是已是可观察的对象了,就直接返回这个对象
  3. 判断第一个参数是什么类型,而后调用不一样的方法, 总共有三种类型:
    object
    ,
    array
    ,
    map
    (ES 的Map 数据类型), 分别调用:observable$$1.object, observable$$1.array, observable$$1.map方法, 那这个observable$$1又是什么呢?在第一行var observable$$1 = createObservable;表面就是createObservable方法。可是这个方法就短短几行代码,并无object, array, map着三个方法, 咱们发如今这个方法下面有observableFactories 对象,其是一个工厂对象,用来给createObservable添加方法,其定义了这三个方法,而且通遍历过Object.keys(observableFactories).forEach(function (name) { return (observable$$1[name] = observableFactories[name]); });

由于在咱们的Demo 中咱们传递的是一个Object, 因此会调用observable$$1.object 方法,接下来咱们在继续分析这个方法, 其代码以下:ide

object: function (props, decorators, options) {
        if (typeof arguments[1] === "string")
            incorrectlyUsedAsDecorator("object");
        var o = asCreateObservableOptions$$1(options);
        if (o.proxy === false) {
            return extendObservable$$1({}, props, decorators, o);
        }
        else {
            var defaultDecorator = getDefaultDecoratorFromObjectOptions$$1(o);
            var base = extendObservable$$1({}, undefined, undefined, o);
            var proxy = createDynamicObservableObject$$1(base);
            extendObservableObjectWithProperties$$1(proxy, props, decorators, defaultDecorator);
            return proxy;
        }
    },
复制代码复制代码

var o = asCreateObservableOptions$$1(options); 生成的了一个简单的对象:ui

var defaultCreateObservableOptions$$1 = {
    deep: true,
    name: undefined,
    defaultDecorator: undefined,
    proxy: true
};
复制代码复制代码

o.proxy 的值为true, 因此会走else 逻辑分支, 因此接下来咱们一一分析else 分支中的每一条代码。spa

  1. var defaultDecorator = getDefaultDecoratorFromObjectOptions$$1(o); 这个是跟装饰器有关的逻辑,咱们先跳过
  2. var base = extendObservable$$1({}, undefined, undefined, o);
    o
    对象进行了加工处理,变成了一个Symbol 数据类型。

这一步操做很是重要,给一个空对象添加了一个$mobx$$1(var $mobx$$1 = Symbol("mobx administration");)的属性, 其值是一个 ObservableObjectAdministration 类型对象,其write 方法在后续数据拦截中会调用。

图3
  1. var proxy = createDynamicObservableObject$$1(base); 这个方法,最为核心, 对这个对象进行了代理(
    Proxy
    )

图4

对这个对象的属性的get, set, has, deleteProperty, ownKeys, preventExtensions方法进行了代理拦截,这个是Mobx 事件数据添加的一个核心点。

  1. 第三点的proxy 其实只是初始化了一个简单的代理对象,可是没有与咱们须要观察的target(也就是mobx.observable方法传递进来的须要被观察的对象)关联起来, extendObservableObjectWithProperties$$1(proxy, props, decorators, defaultDecorator); 方法会遍历target 的属性,将其赋值给proxy对象, 而后咱们mobx.observable 里的对象都被代理了,也就是实现了对属性操做的拦截处理。

  2. 在第四点extendObservableObjectWithProperties$$1 方法中, 最终会给原始的对象的属性进行装饰,经过查看function 的 call stack 得知,最后对调用

    ObservableObjectAdministration
    的addObservableProp 方法, 针对每个propName(原始对象的Key)生成一个
    ObservableValue
    对象,而且保存在
    ObservableObjectAdministration
    对象的values

图三中发现, 真正实现数据拦截的就是objectProxyTraps 拦截器, 下一章节,咱们须要对这个拦截器进行深刻分析,着重看get,set如何实现了数据拦截。

  1. return proxy; 最终将返回一个已经被代理过的对象,替换原生对象。

bankUser 对象就是一个已经被代理了的对象,而且包含了一个Symbol 类型的新的属性。

const bankUser = mobx.observable({
    name: 'Ivan Fan',
    income: 3,
    debit: 2
});
复制代码复制代码

总结

  1. observable 首先传入一个原始对象(能够传入多种类型的数据: array, map, object, 如今只分析object 类型的状况)
  2. 建立一个空的Object 对象,而且添加一些默认属性(var base = extendObservable$$1({}, undefined, undefined, o);), 包括一个Symbol类型的属性,其值是一个ObservableObjectAdministration 类型的对象.
  3. 将这个对象用ES6
    Proxy
    进行了代理, 会拦截这个对象的一些列操做(get, set...) var proxy = new Proxy(base, objectProxyTraps);
  4. 将原始对象,进行遍历,将其全部的本身的属性挂载在新建立的空对象中
  5. 返回已经加工处理的对象bankUser
  6. 后续就能够监听这个对象的相应的操做了。
  7. 加工后的对象以下图所示, 后面操做的对象,就是以下这个对象,可是observable 方法,其实只是作到了以下图的第二步(2), 第三步(3)的
    observers
    属性仍是一个没有任何值的Set 对象,在后续分析autorun 方法中,会涉及到在何时去给它赋值

相关文章
相关标签/搜索