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(上)
第五篇 前言 本篇主要讲解图片缓存类的知识,虽然只涉及了图片方面的缓存的设计,但思想同样适用于别的方面的设计.在架构上来说,缓存算是存储设计的一部分.我们把各种不同的存储内容按照功能进行切割后,图片缓 ...
随机推荐
- vmware12无法打开内核设备“\\.\Global\vmx86”
vmware12 无法打开内核设备"\\.\Global\vmx86": 系统找不到指定的文件.你想要在安装 VMware Workstation 前重启吗? 打开vmware12 ...
- PHP静态变量定义memcache对象的调用
<?php $do = $_GET['do']; myCache($do,'do'); echo myCache($do); /** * 缓存管理 * @param mixed $name 缓存 ...
- jQuery的一些笔记
1.区别jQuery对象和DOM对象. var text1=document.getElementByTagName("div") var $text2=$("div&q ...
- Windows平台分布式网站系统应用(转)
概述 最近.NET的世界开始闹腾了,微软官方终于加入到了对.NET跨平台的支持,并且在不久的将来,我们在VS里面写的代码可能就可以通过Mono直接在Linux和Mac上运行.那么大家(开发者和企业)为 ...
- vim学习
vim编辑器的工作模式分为3种 1.Command Mode 命令模式 2.Insert Mode 插入模式 3.Lastline Mode 底行模式 vim 打开文件时处于命令模式,i 可以切换到插 ...
- Oracle 常见函数
1.把数字转换为字符串:To_char(数字) 2.Oracle 拼接字符串: || ':' || select 字段1 || ':' || 字段2 from table :字段1:字段2 3. ...
- 基于Windows服务器集群的Redis主从配置指南
前段时间一个项目因并发量大.因防止宕机做了主从备份,首页的表连接查询又非常的耗时.故此拿出利器Redis缓存这个查询结果,并随着用户操作而更新. 因官方目前只有linux版,Windows版下载: ...
- WCF框架处理流程初探
拜读了大牛蒋金楠的<WCF技术剖析之一:通过一个ASP.NET程序模拟WCF基础架构>,写点心得. (原文:http://www.cnblogs.com/artech/archive/20 ...
- mongoDB数据库
1.mongoDB简介:mongoDB 为非关系数据库,集合(collection)关系数据库中的表,中存储的为json结构的文档,集合中的每一条记录都可以结构不同, 但必须都有_id字段(mongo ...
- 转——JAVA中calendar,date,string 的相互转换和详细用法
package cn.outofmemory.codes.Date; import java.util.Calendar; import java.util.Date; public class Ca ...