mobx源码解读1
mobx是redux的代替品,其本身就是一个很好的MVVM框架。因此花点力气研究一下它。
网上下最新的2.75
function Todo() {
this.id = Math.random()
mobx.extendObservable(this, {
aaa: 111,
bbb: 222
})
}
var vm = new Todo
mobx.autorun(function () {
console.log(vm.aaa + " " + vm.bbb)
})
这是es5的写法,可以改成更酷的es7写法
import {observable,autorun} from "mobx";
class Todo {
@observable aaa:number = 0;
@observable bbb:number = 1;
}
cont vm = new Todo
autorun(() => console.log(vm.aaa+" "+ vm.bbb ) );
语法怎么也没关系,重要的是思想。我们看这里面出现 的两个方法extendObservable与autorun
function extendObservable(target) {
//将其他参数混入第一个参数中,有点像jQuery的extend
var properties = [];
for (var _i = 1; _i < arguments.length; _i++) {
properties[_i - 1] = arguments[_i];
}
invariant(arguments.length >= 2, "extendObservable expected 2 or more arguments");
invariant(typeof target === "object", "extendObservable expects an object as first argument");
invariant(!(isObservableMap(target)), "extendObservable should not be used on maps, use map.merge instead");
properties.forEach(function (propSet) {
invariant(typeof propSet === "object", "all arguments of extendObservable should be objects");
invariant(!isObservable(propSet), "extending an object with another observable (object) is not supported. Please construct an explicit propertymap, using `toJS` if need. See issue #540");
extendObservableHelper(target, propSet, ValueMode.Recursive, null);
});
return target;
}
mobx从react那里借鉴了invariant,如果第一个参数为false时,它就会将第二个参数打印出来。通过babel等编译工具,可能在生产环境中将它们从最终代码里完全剔除。这么密集的提示信息,是目前所有JS中绝无紧有的。这在我们开发阶段非常有帮助。
这个方法无非是告诉我们,第一个参数必须是对象,并且不能被Observable化(isObservableMap),第二,第三,第四个参数也是如此。后面的对象的个数是随机,但必须保证有一个。最后是调用了extendObservableHelper方法。
function extendObservableHelper(target, properties, mode, name) {
var adm = asObservableObject(target, name, mode);
for (var key in properties)
if (hasOwnProperty(properties, key)) {
if (target === properties && !isPropertyConfigurable(target, key))
continue;
var descriptor = Object.getOwnPropertyDescriptor(properties, key);
setObservableObjectInstanceProperty(adm, key, descriptor);
}
return target;
}
extendObservableHelpery方法,首先根据第一个对象,产生一个特殊的对象,这与avalon的策略一样。不在原对象上修改,而是生成另一个可观察对象(观察者模式中,Observable, 对应的是Observe),这样就有机会用到Proxy这样更高层次的魔术对象。
这个步骤由asObservableObject方法处理,它有三个参数,第二个应该用来生成UUID的,第三个是产生模式。
function asObservableObject(target, name, mode) {
if (mode === void 0) { mode = ValueMode.Recursive; }
if (isObservableObject(target))
return target.$mobx;
if (!isPlainObject(target))
name = (target.constructor.name || "ObservableObject") + "@" + getNextId();
if (!name)
name = "ObservableObject@" + getNextId();
var adm = new ObservableObjectAdministration(target, name, mode);
addHiddenFinalProp(target, "$mobx", adm);
return adm;
}
我猜得不错,asObservableObject里面,如果它已经被改造,立即返回,否则就将它变成ObservableObjectAdministration实例。其中name,它会先定义目标对象是用户类的实例,是就取其构造器名+UUID,否则就是ObservableObject+UUID。然后为原对象添加一个属性$mobx,这个属性是隐藏的,不可修改的。我们通过es5 的方法就可以添加此类属性:
function addHiddenFinalProp(object, propName, value) {
Object.defineProperty(object, propName, {
enumerable: false,
writable: false,
configurable: true,
value: value
});
}
ObservableObjectAdministration这个类如下:
function ObservableObjectAdministration(target, name, mode) {
this.target = target;
this.name = name;
this.mode = mode;
this.values = {};
this.changeListeners = null;
this.interceptors = null;
}
ObservableObjectAdministration.prototype.observe = function (callback, fireImmediately) {
invariant(fireImmediately !== true, "`observe` doesn't support the fire immediately property for observable objects.");
return registerListener(this, callback);
};
ObservableObjectAdministration.prototype.intercept = function (handler) {
return registerInterceptor(this, handler);
};
这里最重要的是用到两个内部方法registerListener与registerInterceptor。不过以后讲吧,我们回过头来extendObservableHelper.得到ooa实例后,框架遍历对象的每人属性,然后将当中的可以修改的属性再改造一下,放到ooa实例上。
筛选可用属性通过两个方法hasOwnProperty与isPropertyConfigurable。
setObservableObjectInstanceProperty需要用一个属性的描述对象。描述对象也是es5的,不懂的人可以翻看MSDN。一个属性描述对象有6个可用的配置项:value, writable, enumerable, configurable, get, set。注意,这里是说可用,并不代表一个对象就有6个属性,它们最多有4个属性,有了value, writable就不能get,set,反之亦然。因此是存在两种形态的描述对象。我们可以姑且称之为,数据描述与方法描述(也叫访问器描述)
function setObservableObjectInstanceProperty(adm, propName, descriptor) {
if (adm.values[propName]) {//如果它已经在实例是注册了,就改变其数据值
invariant("value" in descriptor, "cannot redefine property " + propName);
adm.target[propName] = descriptor.value;
}
else {
if ("value" in descriptor) {//如果是数据描述
if (handleAsComputedValue(descriptor.value)) {
if (deprecated(COMPUTED_FUNC_DEPRECATED)) {
console.error("in: " + adm.name + "." + propName);
console.trace();
}
}
defineObservableProperty(adm, propName, descriptor.value, true, undefined);
} else {
defineObservableProperty(adm, propName, descriptor.get, true, descriptor.set);
}
}
}
比如本文最初的例子,aaa和bbb,它们的描述对象就是数据描述。
function defineObservableProperty(adm, propName, newValue, asInstanceProperty, setter) {
if (asInstanceProperty)
assertPropertyConfigurable(adm.target, propName);
var observable;
var name = adm.name + "." + propName;
var isComputed = true;
if (isComputedValue(newValue)) {
observable = newValue;
newValue.name = name;
if (!newValue.scope)
newValue.scope = adm.target;
}
else if (handleAsComputedValue(newValue)) {
observable = new ComputedValue(newValue, adm.target, false, name, setter);
}
else if (getModifier(newValue) === ValueMode.Structure && typeof newValue.value === "function" && newValue.value.length === 0) {
observable = new ComputedValue(newValue.value, adm.target, true, name, setter);
}
else {
isComputed = false;
if (hasInterceptors(adm)) {
var change = interceptChange(adm, {
object: adm.target,
name: propName,
type: "add",
newValue: newValue
});
if (!change)
return;
newValue = change.newValue;
}
observable = new ObservableValue(newValue, adm.mode, name, false);
newValue = observable.value;
}
adm.values[propName] = observable;
if (asInstanceProperty) {
Object.defineProperty(adm.target, propName, isComputed ? generateComputedPropConfig(propName) : generateObservablePropConfig(propName));
}
if (!isComputed)
notifyPropertyAddition(adm, adm.target, propName, newValue);
}
这方法特复杂,其实与avalon2.1的modelAdaptor的功能一样,将一个属性值,转换为各种类型的可观察对象。可观察对象是有许多各形式的,在avalon中就分为用户VM,子VM,代理VM,复合VM,监控数组,监控属性与计算属性。就像mobx将各类VM合并一下,也有4类。mobx的分类基本与avalon一致。我们还是回到源码上,搞定几个内部方法吧。
assertPropertyConfigurable是用发出警告,不让用户修改描述对象的 configurable配置项。
isComputedValue是对象已经是计算属性了。这个判定非常复杂,本身是由createInstanceofPredicate方法创建的。从源码上看,只要这个对象有一个叫isMobXComputedValue的属性,其值为true就行了。
var isComputedValue = createInstanceofPredicate("ComputedValue", ComputedValue);
function createInstanceofPredicate(name, clazz) {
var propName = "isMobX" + name;
clazz.prototype[propName] = true;
return function (x) {
return isObject(x) && x[propName] === true;
};
}
handleAsComputedValue则用来判定用户的原始数据是否有资格转换为计算属性。虽然说属性,但里面却是一个个对象。mobx里面尽是这样笨重的对象组成。
function handleAsComputedValue(value) {//要求是函数,不能在定义时指定参数,不能是Action
return typeof value === "function" && value.length === 0 && !isAction(value);
}
ObservableValue 这个类巨复杂,暂时跳过,我们看一下行的getModifier,这是用来判定用户函数是否加上了一个特殊的属性。
function getModifier(value) {
if (value) {
return value.mobxModifier || null;
}
return null;
}
否则它就会将属性转换为监控属性(ObservableValue的实例)。
最后为监控属性与计算属性生成新的描述对象
var observablePropertyConfigs = {};
var computedPropertyConfigs = {};
function generateObservablePropConfig(propName) {
var config = observablePropertyConfigs[propName];
if (config)
return config;
return observablePropertyConfigs[propName] = {
configurable: true,
enumerable: true,
get: function () {
return this.$mobx.values[propName].get();
},
set: function (v) {
setPropertyValue(this, propName, v);
}
};
}
function generateComputedPropConfig(propName) {
var config = computedPropertyConfigs[propName];
if (config)
return config;
return computedPropertyConfigs[propName] = {
configurable: true,
enumerable: false,
get: function () {
return this.$mobx.values[propName].get();
},
set: function (v) {
return this.$mobx.values[propName].set(v);
}
};
}
下面这句应该是将消息发到事件总线上,然后用触发视图更新什么的,也是巨复杂。
function notifyPropertyAddition(adm, object, name, newValue) {
var notify = hasListeners(adm);
var notifySpy = isSpyEnabled();
var change = notify || notifySpy ? {
type: "add",
object: object, name: name, newValue: newValue
} : null;
if (notifySpy)
spyReportStart(change);
if (notify)
notifyListeners(adm, change);
if (notifySpy)
spyReportEnd();
}
这是Todo类生成的实例,被硬塞了许多东西:

最后我们看autorun,是不是有一种崩溃的感觉,就像发掘某个人的黑历史,目不睱接!
function autorun(arg1, arg2, arg3) {
var name, view, scope;
if (typeof arg1 === "string") {
name = arg1;
view = arg2;
scope = arg3;
}
else if (typeof arg1 === "function") {
name = arg1.name || ("Autorun@" + getNextId());
view = arg1;
scope = arg2;
}
assertUnwrapped(view, "autorun methods cannot have modifiers");
invariant(typeof view === "function", "autorun expects a function");
invariant(isAction(view) === false, "Warning: attempted to pass an action to autorun. Actions are untracked and will not trigger on state changes. Use `reaction` or wrap only your state modification code in an action.");
if (scope)
view = view.bind(scope);
var reaction = new Reaction(name, function () {
this.track(reactionRunner);
});
function reactionRunner() {
view(reaction);
}
reaction.schedule();
return reaction.getDisposer();
}
看源码,我们的例子就走第二个分支,并且没有传入scope,相当于直接
var reaction = new Reaction('AutoRun@11', function () {
this.track(reactionRunner);
});
var vew = function () {
console.log(vm.aaa + " " + vm.bbb)
}
function reactionRunner() {
view(reaction);
}
Reaction也是巨复杂的,我们看一下它的真身吧,下一节详解
function Reaction(name, onInvalidate) {
if (name === void 0) { name = "Reaction@" + getNextId(); }
this.name = name;
this.onInvalidate = onInvalidate;
this.observing = [];
this.newObserving = [];
this.dependenciesState = IDerivationState.NOT_TRACKING;
this.diffValue = 0;
this.runId = 0;
this.unboundDepsCount = 0;
this.__mapid = "#" + getNextId();
this.isDisposed = false;
this._isScheduled = false;
this._isTrackPending = false;
this._isRunning = false;
}
Reaction.prototype.onBecomeStale = function () {
this.schedule();
};
Reaction.prototype.schedule = function () {
if (!this._isScheduled) {
this._isScheduled = true;
globalState.pendingReactions.push(this);
startBatch();
runReactions();
endBatch();
}
};
Reaction.prototype.isScheduled = function () {
return this._isScheduled;
};
Reaction.prototype.runReaction = function () {
if (!this.isDisposed) {
this._isScheduled = false;
if (shouldCompute(this)) {
this._isTrackPending = true;
this.onInvalidate();
if (this._isTrackPending && isSpyEnabled()) {
spyReport({
object: this,
type: "scheduled-reaction"
});
}
}
}
};
Reaction.prototype.track = function (fn) {
startBatch();
var notify = isSpyEnabled();
var startTime;
if (notify) {
startTime = Date.now();
spyReportStart({
object: this,
type: "reaction",
fn: fn
});
}
this._isRunning = true;
trackDerivedFunction(this, fn);
this._isRunning = false;
this._isTrackPending = false;
if (this.isDisposed) {
clearObserving(this);
}
if (notify) {
spyReportEnd({
time: Date.now() - startTime
});
}
endBatch();
};
Reaction.prototype.recoverFromError = function () {
this._isRunning = false;
this._isTrackPending = false;
};
Reaction.prototype.dispose = function () {
if (!this.isDisposed) {
this.isDisposed = true;
if (!this._isRunning) {
startBatch();
clearObserving(this);
endBatch();
}
}
};
Reaction.prototype.getDisposer = function () {
var r = this.dispose.bind(this);
r.$mobx = this;
return r;
};
Reaction.prototype.toString = function () {
return "Reaction[" + this.name + "]";
};
Reaction.prototype.whyRun = function () {
var observing = unique(this._isRunning ? this.newObserving : this.observing).map(function (dep) { return dep.name; });
return ("\nWhyRun? reaction '" + this.name + "':\n * Status: [" + (this.isDisposed ? "stopped" : this._isRunning ? "running" : this.isScheduled() ? "scheduled" : "idle") + "]\n * This reaction will re-run if any of the following observables changes:\n " + joinStrings(observing) + "\n " + ((this._isRunning) ? " (... or any observable accessed during the remainder of the current run)" : "") + "\n\tMissing items in this list?\n\t 1. Check whether all used values are properly marked as observable (use isObservable to verify)\n\t 2. Make sure you didn't dereference values too early. MobX observes props, not primitives. E.g: use 'person.name' instead of 'name' in your computation.\n");
};
return Reaction;
总而言之,你看完这篇,你还是无法了解它是怎么运作的。每个人实现MVVM的方式都不一样。但MVVM都有一个共同点,就是收集依赖与触发通知。目前,我们已经看到notify这样的字眼了。我们下篇就是寻找它收集依赖的根据!
mobx源码解读1的更多相关文章
- mobx源码解读3
计算属性 function Todo() { this.id = Math.random() mobx.extendObservable(this, { aaa: 222, bbb: 11, ccc: ...
- mobx源码解读4
这节介绍一下mobx的变动因子的稳定性. mobx整个系统是由ObservableValue, ComputedValue, Reaction这三个东西构建的 ObservableValue 是最小的 ...
- mobx源码解读2
我们将上节用到的几个类的构造器列举一下吧: function Reaction(name, onInvalidate) { if (name === void 0) { name = "Re ...
- SDWebImage源码解读之SDWebImageDownloaderOperation
第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...
- SDWebImage源码解读 之 NSData+ImageContentType
第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...
- SDWebImage源码解读 之 UIImage+GIF
第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...
- SDWebImage源码解读 之 SDWebImageCompat
第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...
- SDWebImage源码解读_之SDWebImageDecoder
第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...
- SDWebImage源码解读之SDWebImageCache(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
随机推荐
- css之position相对定位和绝对定位
一.position的四个值:static.relative.absolute.fixed. 绝对定位:absolute和fixed统称为绝对定位 相对定位:relative 默认值:static 二 ...
- tokudb引擎磁盘空间不足导致写入失败的调查
故障现象 2016.1.1号早上4点左右,zabbi数据库服务器报警,写入数据失败.登陆机器后检查发现磁盘空间使用95%没有用满,进去zabbix数据库,执行insert命令提示错误“errir 10 ...
- Visual Studio 14 初试,vNext
下了几天的VS 2014 .终于安装上了,花了好几天时间, VS 2014 下载地址, http://www.visualstudio.com/en-us/downloads/visual-stud ...
- Spark常用函数(源码阅读六)
源码层面整理下我们常用的操作RDD数据处理与分析的函数,从而能更好的应用于工作中. 连接Hbase,读取hbase的过程,首先代码如下: def tableInitByTime(sc : SparkC ...
- 关于ios使用jquery的on,委托事件失效
$('.parents').on("click",'.child',function(){}); 类似上面这种,在ios上点击"child"元素不会起作用,解决 ...
- (.text+0x12): undefined reference to `rpl_fprintf'
问题1:(.text+0x12): undefined reference to `rpl_fprintf'解决办法:在yacc前面添加%{#undef yyerrorvoid yyerror (ch ...
- iOS UIButton单双击处理响应不同的方法
//显示目标 双击显示当前用户坐标位置 UIButton * btnShowDistination = [[UIButton alloc]initWithFrame:CGRectMake(, SCRE ...
- 每天一个 Linux 命令(22):find 命令的参数详解
find一些常用参数的一些常用实例和一些具体用法和注意事项. 1.使用name选项: 文件名选项是find命令最常用的选项,要么单独使用该选项,要么和其他选项一起使用. 可以使用某种文件名模式来匹配 ...
- super getClass()
首先看一段代码: import java.util.Date;public class Test extends Date{ public static void main(String[] args ...
- Shade Exaple1
Shader "Custom/Diffuse Texture" { Properties { _MainTex ("Base (RGB)", 2D) = &qu ...