前言
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;
}
}
- 参数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点进行详细的介绍
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的时候再展开。
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;
}
asCreateObservableOptions和getDefaultDecoratorFromObjectOptions前面都已经介绍过了,这里就不做赘述,重点来看一下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的一个实例。
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)源码解读二 autorun及mobx(5.14.0)源码解读三 observable更新如何触发autorun回调文章中做详细介绍。
(2) 为$mobx属性添加proxy属性
**总之, proxy是一个具有拦截行为的Proxy对象,target为第3点返回的包含$mobx属性的base, 这里只是实例化了一个proxy对象,该对象中除了$mobx,还什么都没有,真正起作用的在第5点
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)源码解读二 autorun、mobx(5.14.0)源码解读三 observable更新如何触发autorun回调中介绍,这里不做赘述。
(2)为$mobx添加key为属性名,值为generateObservablePropConfig(propName)的属性
总之extendObservableObjectWithProperties就是属性值,包装成ObservableValue类型,存储在$mobx.values中,同时将传入的属性添加到$mobx中
return proxy最终vipMember被包装成如下所示的结构
注意,这时候绿框内的observers是空的,这个将会在autorun的时候进行依赖收集。
总结
observable主要做了以下几件事:
- 创建一个空对象,并设置默认属性$mobx(即Symbol("mobx administration"))
- 将该对象转化成一个Proxy实例,从此对该对象的属性进行get、set操作都将被拦截
- 遍历传入对象(即observable的参数)的属性,将其添加到之前创建的空对象中,同时将属性值转化成ObservableValue实例,set到$mobx.values中。
- 返回Proxy实例,即原来的原始对象vipMember现在已经被替换成了一个包含原有属性及$mobx属性的对象。