博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Mobx 源码解析
阅读量:6800 次
发布时间:2019-06-26

本文共 5835 字,大约阅读时间需要 19 分钟。

前言

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

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

我直接从 下载了一份, 然后进行分析。

Demo

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

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 --})复制代码复制代码

我们的界面非常简单,如图:

图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 两个方法,分别是

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

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

observable,翻译成中文就是可以观测的, 我们现在来调试这个方法, 我们可以const bankUser = mobx.observable({

这一行打一个断点,然后F11,跳进去,发现源码对应的是一个

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)'");}复制代码复制代码

上面代码很简单,参数有三个,但是我们在调用的时候,值传递了一个参数, 所以我们暂且只要关心第一个参数

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 方法,接下来我们在继续分析这个方法, 其代码如下:

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); 生成的了一个简单的对象:

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

o.proxy 的值为true, 所以会走else 逻辑分支, 所以接下来我们一一分析else 分支中的每一条代码。

  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 方法中,会涉及到在什么时候去给它赋值

转载地址:http://rcywl.baihongyu.com/

你可能感兴趣的文章
Linux 小知识翻译 - 「环境变量」
查看>>
js提取正则中的字符串
查看>>
js38---门面模式
查看>>
转:SharePoint Portal Server扩容部署
查看>>
libtiff库使用
查看>>
[转载]UML用例图总结
查看>>
《深度探索C++对象模型》调用虚函数
查看>>
“ORA-01747: user.table.column, table.column 或列说明无效” 的解决方案
查看>>
Python命令行解析argparse常用语法使用简介
查看>>
Lambda 表达式
查看>>
手把手教你用python抢票回家过年 !(附代码)
查看>>
SilverLight1.1 之旅(二):添加事件
查看>>
【转】Javascript MD5编码
查看>>
Java:集合类的区别详解
查看>>
linux 内核库函数 【转】
查看>>
asp.net 判断是否是日期格式,判断是否是长日期格式,短日期格式,时间判断,日期判断,全部格式...
查看>>
理解 OpenStack Swift (3):监控和一些影响性能的因素 [Monitoring and Performance]
查看>>
聚会留念
查看>>
iOS:在使用Cocoapods安装shareSDK时出现的link路径错误
查看>>
Asp.net防止页面被多次提交
查看>>