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的更多相关文章

  1. mobx源码解读3

    计算属性 function Todo() { this.id = Math.random() mobx.extendObservable(this, { aaa: 222, bbb: 11, ccc: ...

  2. mobx源码解读4

    这节介绍一下mobx的变动因子的稳定性. mobx整个系统是由ObservableValue, ComputedValue, Reaction这三个东西构建的 ObservableValue 是最小的 ...

  3. mobx源码解读2

    我们将上节用到的几个类的构造器列举一下吧: function Reaction(name, onInvalidate) { if (name === void 0) { name = "Re ...

  4. SDWebImage源码解读之SDWebImageDownloaderOperation

    第七篇 前言 本篇文章主要讲解下载操作的相关知识,SDWebImageDownloaderOperation的主要任务是把一张图片从服务器下载到内存中.下载数据并不难,如何对下载这一系列的任务进行设计 ...

  5. SDWebImage源码解读 之 NSData+ImageContentType

    第一篇 前言 从今天开始,我将开启一段源码解读的旅途了.在这里先暂时不透露具体解读的源码到底是哪些?因为也可能随着解读的进行会更改计划.但能够肯定的是,这一系列之中肯定会有Swift版本的代码. 说说 ...

  6. SDWebImage源码解读 之 UIImage+GIF

    第二篇 前言 本篇是和GIF相关的一个UIImage的分类.主要提供了三个方法: + (UIImage *)sd_animatedGIFNamed:(NSString *)name ----- 根据名 ...

  7. SDWebImage源码解读 之 SDWebImageCompat

    第三篇 前言 本篇主要解读SDWebImage的配置文件.正如compat的定义,该配置文件主要是兼容Apple的其他设备.也许我们真实的开发平台只有一个,但考虑各个平台的兼容性,对于框架有着很重要的 ...

  8. SDWebImage源码解读_之SDWebImageDecoder

    第四篇 前言 首先,我们要弄明白一个问题? 为什么要对UIImage进行解码呢?难道不能直接使用吗? 其实不解码也是可以使用的,假如说我们通过imageNamed:来加载image,系统默认会在主线程 ...

  9. SDWebImage源码解读之SDWebImageCache(上)

    第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...

随机推荐

  1. MariaDB Galera Cluster部署手册

    MariaDB Galera Cluster部署手册 galara保证双主数据库的同步及一致性 1.环境准备 基于新部署.最小化安装centos6.5 1>  yum install opens ...

  2. Visual Studio 14 初试,vNext

    下了几天的VS 2014 .终于安装上了,花了好几天时间, VS 2014  下载地址, http://www.visualstudio.com/en-us/downloads/visual-stud ...

  3. Sybase

    Variable Naming Convention first character can be alphabetic character, _, @. Followed by alphabetic ...

  4. 前端网站helper

    聚合api 一.颜色类网站http://colorhunt.co 这个网站给我们提供了很多的配色方案,我们直接使用就OK了.使用方法也很简单,鼠标移动到对应的颜色上,我们就可以看到颜色的十六进制码,复 ...

  5. boost相关

    1 boost 常用函数 <1> tcp跟udp的收发函数名 tcp收发 async_write async_read async_read_until udp收发 async_send_ ...

  6. [杂] 将高版本iTunes备份恢复到低版本iOS设备中

    除非开发测试用设备,自用设备不要随便升iOS beta,不要随便升iOS beta,不要随便升iOS beta. 对于升级了高版本iOS的用户,默认情况下重刷低版本iOS时,iTunes不允许向低版本 ...

  7. JsonException: Max allowed object depth reached while trying to export from type System.Single

    在进行类转json字符串时,报错JsonException: Max allowed object depth reached while trying to export from type Sys ...

  8. [转]SQL Server 连接串关键字别名

    转自:http://stackoverflow.com/questions/3077412/what-is-the-difference-between-trusted-connection-and- ...

  9. 【HOW】在InfoPath中如何为浏览和编辑模式设置不同的视图

    1. 在SharePoint Designer中打开要自定义视图的列表.并点击菜单:列表设置 > 在 InfoPath 中设计表单 > {要自定义表单的内容类型},则会自动打开InfoPa ...

  10. (转)打印相关_C#图片处理Bitmap位图缩放和剪裁

    原文地址:http://blog.sina.com.cn/s/blog_6427a6b50101el9d.html 在GDI+中,缩放和剪裁可以看作同一个操作,无非就是原始区域的选择不同罢了. /// ...