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. css3 em rem等单位的区别

    px:绝对单位,页面按精确像素展示 em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算(浏览器默认字体是16px),整个页面内1em不是一个固定的值. rem:相对 ...

  2. Turn off Debug Logging in Quartz .Net

    Quartz.net uses Common.Logging, so something like this in your App.config/Web.config: <configSect ...

  3. MyBatis原理分析之四:一次SQL查询的源码分析

    上回我们讲到Mybatis加载相关的配置文件进行初始化,这回我们讲一下一次SQL查询怎么进行的. 准备工作 Mybatis完成一次SQL查询需要使用的代码如下: ) { ); ) { throw ne ...

  4. 快速开发一个PHP电影爬虫

    今天来做一个PHP电影小爬虫.我们来利用simple_html_dom的采集数据实例,这是一个PHP的库,上手很容易.simple_html_dom 可以很好的帮助我们利用php解析html文档.通过 ...

  5. 传入url地址请求服务器api,浏览器显示图片

    @RequestMapping("/proxyImage") public void proxyImage(HttpServletRequest request, HttpServ ...

  6. ORM框架详解

    .Net开源微型ORM框架测评 什么是ORM? 对象关系映射(英语:Object Relation Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序技术,用于实现面向对象 ...

  7. 如何消除移动端a标签点击时的蓝色底色以及a标签link、visited、hover、active的顺序

    1.消除a标签移动端点击时的蓝色底色 -webkit-tap-highlight-color:transparent 2.link.visited.hover.active的顺序 a:link{tex ...

  8. Asp.Net MVC及Web API框架配置会碰到的几个问题及解决方案

    前言 刚开始创建MVC与Web API的混合项目时,碰到好多问题,今天拿出来跟大家一起分享下.有朋友私信我问项目的分层及文件夹结构在我的第一篇博客中没说清楚,那么接下来我就准备从这些文件怎么分文件夹说 ...

  9. d3安装异常

    使用npm安装D3,发现其工程名和依赖名重复,导致安装异常 http://thisdavej.com/node-newbie-error-npm-refusing-to-install-package ...

  10. 【转】el表达式的判断符

    el表达式一般不直接用==判断是否相等 != > < >= <=之类的表示不等于 大于 小于 大于等于 小于等于,而是使用字母表示的表达式,他们的表示如下: == eq 等于 ...