前言
在Git 找到Mobx 的源码, 发现其是使用TypeScript 编写,因为我对Typescrit 没有项目经验,所以我先会将其编译成JavaScript,所以我们可以运行如下脚本或者从直接下载一份编译过的源码,我们可以选择umd 规范脚本:
git clone git@github.com:mobxjs/mobx.git
npm i
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 --})复制代码复制代码
我们的界面非常简单,如图:
observable
从上面的JS文件中,我们发现其中引用了mobx 两个方法,分别是
Ctrl + K
Ctrl + 0
将代码都折叠起来, 然后在打开, 找到 exports 的代码块,我们可以查看mobx 都暴露出了哪些方法: observable,翻译成中文就是可以观测的, 我们现在来调试这个方法, 我们可以const bankUser = mobx.observable({
这一行打一个断点,然后F11
,跳进去,发现源码对应的是一个
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)'");}复制代码复制代码
上面代码很简单,参数有三个,但是我们在调用的时候,值传递了一个参数, 所以我们暂且只要关心第一个参数
- 如果传入的第二个参数是一个字符串, 则直接调用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
方法在后续数据拦截中会调用。
var proxy = createDynamicObservableObject$$1(base);
这个方法,最为核心, 对这个对象进行了代理(Proxy)
对这个对象的属性的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中
从图三中发现, 真正实现数据拦截的就是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 方法中,会涉及到在什么时候去给它赋值