未加星标

Mobx 源码解析

字体大小 | |
[前端(javascript) 所属分类 前端(javascript) | 发布者 店小二04 | 时间 2019 | 作者 红领巾 ] 0人收藏点击收藏
前言

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

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

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

Demo

首先我们从一个最基本的 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 -- }) 复制代码复制代码

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


Mobx 源码解析

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

observable

和autorun,是的,是这样两个方法,让 incomeLabel

在点击按钮的时候实时的发生了变化,所以我们接下来会对这两个方法进行深入分析,这一章节我们会先分析

observable

先进行分析。 我们先打开Mobx的 源码 , 如果我们用 Vscode 打开这个源码,我们可以用快捷键 Ctrl + K Ctrl + 0 将代码都折叠起来, 然后在打开, 找到 exports

的代码块,我们可以查看mobx 都暴露出了哪些方法:


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 的基本逻辑:

如果传入的第二个参数是一个字符串, 则直接调用deepDecorator$$1.apply(null, arguments); 判断第一个参数是否已经是一个可观察的对象了,如果已经是可观察的对象了,就直接返回这个对象

判断第一个参数是什么类型,然后调用不同的方法, 总共有三种类型:

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 分支中的每一条代码。

var defaultDecorator = getDefaultDecoratorFromObjectOptions$$1(o); 这个是跟装饰器有关的逻辑,我们先跳过 var base = extendObservable$$1({}, undefined, undefined, o);

o

对象进行了加工处理,变成了一个 Symbol

数据类型。

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


Mobx 源码解析

图3

var proxy = createDynamicObservableObject$$1(base); 这个方法,最为 核心

, 对这个对象进行了代理(

Proxy

)


Mobx 源码解析

图4

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

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

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

ObservableObjectAdministration

的addObservableProp 方法, 针对每一个 propName

(原始对象的Key)生成一个

ObservableValue

对象,并且保存在

ObservableObjectAdministration

对象的 values


Mobx 源码解析

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

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

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

const bankUser = mobx.observable({ name: 'Ivan Fan', income: 3, debit: 2 }); 复制代码复制代码 总结 observable 首先传入一个原始对象(可以传入多种类型的数据: array , map , object , 现在只分析object 类型的情况) 创建一个空的Object 对象,并且添加一些默认属性( var base = extendObservable$$1({}, undefined, undefined, o); ), 包括一个 Symbol 类型的属性,其值是一个 ObservableObjectAdministration 类型的对象. 将这个对象用 ES6

Proxy

进行了代理, 会拦截这个对象的一些列操作( get , set ...) var proxy = new Proxy(base, objectProxyTraps); 将原始对象,进行遍历,将其所有的自己的属性挂载在新创建的空对象中 返回已经加工处理的对象 bankUser 后续就可以监听这个对象的相应的操作了。 加工后的对象如下图所示, 后面操作的对象,就是如下这个对象,但是 observable

方法,其实只是做到了如下图的第二步(2), 第三步(3)的

observers

属性还是一个没有任何值的Set 对象,在后续分析 autorun

方法中,会涉及到在什么时候去给它赋值


Mobx 源码解析

本文前端(javascript)相关术语:javascript是什么意思 javascript下载 javascript权威指南 javascript基础教程 javascript 正则表达式 javascript设计模式 javascript高级程序设计 精通javascript javascript教程

代码区博客精选文章
分页:12
转载请注明
本文标题:Mobx 源码解析
本站链接:https://www.codesec.net/view/628530.html


1.凡CodeSecTeam转载的文章,均出自其它媒体或其他官网介绍,目的在于传递更多的信息,并不代表本站赞同其观点和其真实性负责;
2.转载的文章仅代表原创作者观点,与本站无关。其原创性以及文中陈述文字和内容未经本站证实,本站对该文以及其中全部或者部分内容、文字的真实性、完整性、及时性,不作出任何保证或承若;
3.如本站转载稿涉及版权等问题,请作者及时联系本站,我们会及时处理。
登录后可拥有收藏文章、关注作者等权限...
技术大类 技术大类 | 前端(javascript) | 评论(0) | 阅读(229)