Mobx 源码解读(二) Observable

Mobx 提供了三种可观察的数据类型:对象、数组和 Map。Mobx 内部做了大量的工作,使它们的使用体验和原生 JS 类型一致,通过 observable api 包装后就可以转换成可观察值,使用时无须额外的方法调用。对于其它类型的值,Mobx 提供了 observable.box,包装之后使用其 get, set 方法来获取和设置值,也可以达到「可观察」的效果。

第一篇中提到,Observable 使用 reportObserved 和 propagateChanged 函数通知自身「被观察」和「发生变化」。将值变得可观察的关键步骤就在于触发这两个函数的调用,先来看看不同类型的 Observable 是如何实现这一点的。

如何变得「可观察」

Mobx 提供的 API observable,实际上是一个工厂函数 createObservable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function createObservable(v: any = undefined) {
// observable 作为属性装饰器使用
// 默认使用 deepDecorator,它是 deepEnhancer 对应的 Decorator
if (typeof arguments[1] === "string") return deepDecorator.apply(null, arguments)

if (isObservable(v)) return v

// 默认使用 deepEnhancer 包装
const res = deepEnhancer(v, undefined, undefined)

// 包装后转换为了「可观察的」的数据结构
if (res !== v) return res

// 否则,调用 observable.box 包装
return observable.box(v)
}

// 导出 observable api
export const observable: IObservableFactory &
IObservableFactories & {
deep: {
struct<T>(initialValue?: T): T
}
ref: {
struct<T>(initialValue?: T): T
}
} = createObservable as any

deepEnhancer 是 Mobx 默认的 Modifier(关于 Modifier 可以参看第三篇),会对对象、数组和 Map 进行处理:

1
2
3
4
5
6
7
8
9
function deepEnhancer(v, _, name) {
if (isObservable(v)) return v

if (Array.isArray(v)) return observable.array(v, name)
if (isPlainObject(v)) return observable.object(v, name)
if (isES6Map(v)) return observable.map(v, name)

return v
}

可以看到,对于其他类型的值,deepEnhancer 原样返回,从而在 createObservable 中会使用 observable.box 进行包装。先来看看最简单的 observable.box 如何使传入的值可观察。

Box

observable.box 方法简单地返回一个 ObservableValue 实例。

1
2
3
4
box<T>(value?: T, name?: string): IObservableValue<T> {
if (arguments.length > 2) incorrectlyUsedAsDecorator("box")
return new ObservableValue(value, deepEnhancer, name)
}

ObservableValue 实现了 get 和 set 方法,用户在使用时自行使用这两个方法获取和设置「可观察原始值」的值,那么实现「可观察」就只需在这两个方法内分别去调用 reportObserved 和 reportChanged 即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public set(newValue: T) {
const oldValue = this.value
newValue = this.prepareNewValue(newValue) as any
if (newValue !== UNCHANGED) {
// ...
// 发生变化,设置新值
this.setNewValue(newValue)
}
}

// 做一些新值的预处理工作
private prepareNewValue(newValue): T | IUNCHANGED {
// 检查两个边界情况
checkIfStateModificationsAreAllowed(this)
// ...
// 经过 Enhancer 包装
newValue = this.enhancer(newValue, this.value, this.name)
return this.value !== newValue ? newValue : UNCHANGED
}

setNewValue(newValue: T) {
const oldValue = this.value
this.value = newValue
// reportChanged 方法继承自 BaseAtom
this.reportChanged()
}

public get(): T {
// reportObserved 方法继承自 BaseAtom
this.reportObserved()
// 使用 Enhancer 相应的 Dehancer 获取包装前的值
return this.dehanceValue(this.value)
}

对象

observable.object 方法将对象变为可观察的,它实际上是把对象的所有属性转换为可观察的,存放到一个代理对象上,以减少对原对象的污染:

ObservableObject示意图

1
2
3
4
5
6
7
8
9
10
// observable.object
object<T>(props: T, name?: string): T & IObservableObject {
if (arguments.length > 2) incorrectlyUsedAsDecorator("object")
const res = {}
// 创建代理对象
asObservableObject(res, name)
// 添加可观察属性
extendObservable(res, props)
return res as any
}

asObservableObject 函数为对象创建一个「可观察对象」作为代理对象,按照源码中的命名,我们称之为「admin 对象」。admin 对象的 target 属性指向原对象,并作为隐藏属性添加到原对象上。它的实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function asObservableObject(target, name?: string): ObservableObjectAdministration {
// admin 对象已经存在,直接返回
if (isObservableObject(target) && target.hasOwnProperty("$mobx")) return (target as any).$mobx

if (!isPlainObject(target))
name = (target.constructor.name || "ObservableObject") + "@" + getNextId()
if (!name) name = "ObservableObject@" + getNextId()

// 创建 admin 对象
const adm = new ObservableObjectAdministration(target, name)
// 作为隐藏属性添加到原对象上
addHiddenFinalProp(target, "$mobx", adm)
return adm
}

admin 对象通过 addHiddenFinalProp 方法,作为一个隐藏属性添加到原对象上。使用 ES5 的 Object.defineProperty 定义一个不可枚举的属性即可实现:

1
2
3
4
5
6
7
8
function addHiddenProp(object: any, propName: string, value: any) {
Object.defineProperty(object, propName, {
enumerable: false,
writable: true,
configurable: true,
value
})
}

admin 对象的 values 属性上用于存放原对象的属性,不同的是,这些属性都是经过 Enhancer 包装过的可观察属性。

接下来就是调用 extendObservable 来生成这些可观察属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function extendObservable<A extends Object, B extends Object>(
target: A,
...properties: B[] // 可以接受多组 props
): A & B {
// 使用 deepEnhancer,对复杂类型的属性会递归执行
return extendObservableHelper(target, deepEnhancer, properties) as any
}

function extendObservableHelper(
target: Object,
defaultEnhancer: IEnhancer<any>,
properties: Object[]
): Object {
const adm = asObservableObject(target)
// 避免多组 props 可能有重复
const definedProps = {}
for (let i = properties.length - 1; i >= 0; i--) {
const propSet = properties[i]
for (let key in propSet)
if (definedProps[key] !== true && hasOwnProperty(propSet, key)) {
definedProps[key] = true
if ((target as any) === propSet && !isPropertyConfigurable(target, key)) continue // #111
const descriptor = Object.getOwnPropertyDescriptor(propSet, key)
// 根据属性描述符定义可观察属性
defineObservablePropertyFromDescriptor(adm, key, descriptor, defaultEnhancer)
}
}
return target
}

defineObservablePropertyFromDescriptor 函数根据属性描述符来定义可观察属性,它是一个通用的函数,后面我们还会多次看到这个函数。对于对象属性,会进入 defineObservableProperty 分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
function defineObservablePropertyFromDescriptor(
adm: ObservableObjectAdministration,
propName: string,
descriptor: PropertyDescriptor,
defaultEnhancer: IEnhancer<any>
) {
// 已经是可观察属性
if (adm.values[propName] && !isComputedValue(adm.values[propName])) {
invariant(
"value" in descriptor,
`The property ${propName} in ${adm.name} is already observable, cannot redefine it as computed property`
)
adm.target[propName] = descriptor.value
return
}

// 该属性是数据属性
if ("value" in descriptor) {
// 如果是 Modifier 的描述符,取出值和enhancer
if (isModifierDescriptor(descriptor.value)) {
const modifierDescriptor = descriptor.value as IModifierDescriptor<any>
defineObservableProperty(
adm,
propName,
modifierDescriptor.initialValue,
modifierDescriptor.enhancer
)
// Action
} else if (isAction(descriptor.value) && descriptor.value.autoBind === true) {
defineBoundAction(adm.target, propName, descriptor.value.originalFn)
// 计算值
} else if (isComputedValue(descriptor.value)) {
defineComputedPropertyFromComputedValue(adm, propName, descriptor.value)
} else {
// 定义可观察属性
defineObservableProperty(adm, propName, descriptor.value, defaultEnhancer)
}
// 访问器属性(即计算值)
} else {
defineComputedProperty(
adm,
propName,
descriptor.get,
descriptor.set,
comparer.default,
true
)
}
}

defineObservableProperty 为对象定义可观察属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function defineObservableProperty(
adm: ObservableObjectAdministration,
propName: string,
newValue,
enhancer: IEnhancer<any>
) {
// ...

// 将属性定义成一个 ObservableValue 实例,存放在 admin 对象的 values 属性上
// 注意这里使用的是 deepEnhancer,意味着对于属性值是复杂对象的情形,在实例化
// ObservableValue 时会递归执行「将对象转换为可观察对象」的过程,从而使得 values
// 上的属性都是可观察的
const observable = (adm.values[propName] = new ObservableValue(
newValue,
enhancer,
`${adm.name}.${propName}`,
false
))
newValue = (observable as any).value

// 定义访问器属性
Object.defineProperty(adm.target, propName, generateObservablePropConfig(propName))
}

重点来了,前面提到,ObservableValue 实现了 get 和 set 方法,对于原始值,由用户负责主动调用这两个方法,从而触发 reportObserved 和 reportChanged(实际上是在 setNewValue 方法中触发)。

那么对于可观察属性,将其包装成 ObservableValue 实例存放在 admin 对象上之后,如何触发 get 和 setNewValue 方法呢?答案是利用访问器属性,generateObservablePropConfig 为属性生成访问器属性描述符,在 get 和 set 访问器中触发相应 ObservableValue 实例的 get 和 setNewValue 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
const observablePropertyConfigs = {} // 属性描述符缓存
function generateObservablePropConfig(propName) {
return (
observablePropertyConfigs[propName] ||
(observablePropertyConfigs[propName] = {
configurable: true,
enumerable: true,
get: function() {
// 调用 admin 对象上存放的相应 ObservableValue 实例的 get 方法
// 从而触发 reportObserved
return this.$mobx.values[propName].get()
},
set: function(v) {
setPropertyValue(this, propName, v)
}
})
)
}

function setPropertyValue(instance, name: string, newValue) {
const adm = instance.$mobx
const observable = adm.values[name]

// 预处理
newValue = observable.prepareNewValue(newValue)

// 值发生变化
if (newValue !== UNCHANGED) {
// setNewValue 会触发 reportChanged
observable.setNewValue(newValue)
}
}

经历完这一系列步骤后,最后返回的就是一个可观察的对象了。

还有一点需要注意的是,因为 extendObservable 中使用的是 deepEnhancer,意味着在 defineObservableProperty 函数中,实例化 ObservableValue 时,如果该属性的值是复杂对象,会递归执行「将对象转换为可观察的」过程,从而使得该属性的所有属性(或元素)也都是可观察的:

1
2
3
4
5
6
7
8
9
10
11
12
// ObservableValue 的构造函数
constructor(
value: T,
protected enhancer: IEnhancer<T>,
name = "ObservableValue@" + getNextId(),
notifySpy = true
) {
super(name)
// 递归调用 deepEnhancer
this.value = enhancer(value, undefined, name)
// ...
}

数组

Mobx 实现了一个扩展的数组类型,ObservableArray,来支持数组的可观察。observable.array 方法返回一个 ObservableArray 实例:

1
2
3
4
5
6
// observable.array 工厂方法
array<T>(initialValues?: T[], name?: string): IObservableArray<T> {
if (arguments.length > 2) incorrectlyUsedAsDecorator("array")
// 返回一个 ObservableArray 实例,使用 deepEnhancer 对各数组项进行包装
return new ObservableArray(initialValues, deepEnhancer, name) as any
}

与对象不同的是,数组的「属性名」都是一样的,即0,1,2…这样的索引。Mobx 利用原型链,在 ObservableArray 的原型上添加 0,1,2…等访问器属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 记录 Observable.prototype 上访问器属性的数量
let OBSERVABLE_ARRAY_BUFFER_SIZE = 0

// 新增访问器属性
function reserveArrayBuffer(max: number) {
for (let index = OBSERVABLE_ARRAY_BUFFER_SIZE; index < max; index++)
createArrayBufferItem(index)
OBSERVABLE_ARRAY_BUFFER_SIZE = max
}

function createArrayBufferItem(index: number) {
Object.defineProperty(ObservableArray.prototype, "" + index, createArrayEntryDescriptor(index))
}

// 访问器属性描述符
function createArrayEntryDescriptor(index: number) {
return {
enumerable: false,
configurable: false,
get: function() {
// 调用 ObservableArray 实例的 get 方法,带上存放在闭包内的索引
return this.get(index)
},
set: function(value) {
// 调用 ObservableArray 实例的 set 方法
this.set(index, value)
}
}
}

// 初始化时先添加1000个索引属性
reserveArrayBuffer(1000)

这样,使用索引访问 ObservableArray 实例中的元素时,顺着原型链查找到相应属性,就会调用 ObservableArray 实例的 get 和 set 方法了,同时带上相应的索引值。

Mobx 初始化时,会在 ObservableArray.prototype 上添加1000个这样的索引属性,当数组长度超过1000时,再通过 reserveArrayBuffer 函数来扩充 ObservableArray.prototype 上索引属性的数量。

也就是说,ObservableArray 实例上并没有0,1,2…等属性,那么数组项存放在哪呢?

和对象的处理类似,每一个 ObservableArray 实例都有一个对应的 ObservableArrayAdministration 实例来管理数组项,数组的每一项都会转换成「可观察的」之后,存放在 admin 对象的 values 属性上,这个属性是一个原生 JS 数组。

ObservableArray, ObservableArray.prototype, ObservableArrayAdministration 三者之间的关系如下图所示:

ObservableArray示意图

来看看一个可观察的数组的初始化过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ObservableArray 的构造函数
constructor(
initialValues: T[] | undefined,
enhancer: IEnhancer<T>,
name = "ObservableArray@" + getNextId(),
owned = false
) {
super()
// 创建 admin 对象,并作为不可枚举、不可修改的属性添加到 ObservableArray 实例上
const adm = new ObservableArrayAdministration<T>(name, enhancer, this as any, owned)
addHiddenFinalProp(this, "$mobx", adm)
// 添加传入的数组项到 admin 对象的 values 属性上
if (initialValues && initialValues.length) {
this.spliceWithArray(0, 0, initialValues)
}
}

spliceWithArray 方法直接调用了 admin 对象上的同名方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ObservableArrayAdministration.prototype.spliceWithArray
spliceWithArray(index: number, deleteCount?: number, newItems?: T[]): T[] {
checkIfStateModificationsAreAllowed(this.atom)
const length = this.values.length

// 一堆参数的处理
if (index === undefined) index = 0
else if (index > length) index = length
else if (index < 0) index = Math.max(0, length + index)

if (arguments.length === 1) deleteCount = length - index
else if (deleteCount === undefined || deleteCount === null) deleteCount = 0
else deleteCount = Math.max(0, Math.min(deleteCount, length - index))

if (newItems === undefined) newItems = []

// 重点:遍历新增的数组项,返回经过 enhancer 包装后的
newItems = <T[]>newItems.map(v => this.enhancer(v, undefined))
const lengthDelta = newItems.length - deleteCount

// 更新 length
this.updateArrayLength(length, lengthDelta)
// 更新 values 数组
const res = this.spliceItemsIntoValues(index, deleteCount, newItems)

if (deleteCount !== 0 || newItems.length !== 0)
// 通知变化
this.notifyArraySplice(index, newItems, res)

// 和原生 splice 方法一样,返回删除的数组项
return this.dehanceValues(res)
}

这样就得到了一个可观察的数组。

当我们访问数组项时,如前文所述,Observable.prototype 上的索引属性被访问,并通过 get 访问器调用 ObservableArray 实例的 get 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
get(index: number): T | undefined {
// 获取 admin 对象
const impl = <ObservableArrayAdministration<any>>this.$mobx
if (impl) {
if (index < impl.values.length) {
// admin 对象上的 Atom 实例通知数组被观察
impl.atom.reportObserved()
// 返回经过 dehancer 处理的原始数组项
return impl.dehanceValue(impl.values[index])
}
}
return undefined
}

直接通过索引修改数组项的值时,set 访问器会调用 ObservableArray 实例的 set 方法,同样也是由 admin 对象负责数组项的更新和变化通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
set(index: number, newValue: T) {
const adm = <ObservableArrayAdministration<T>>this.$mobx
const values = adm.values
if (index < values.length) {
checkIfStateModificationsAreAllowed(adm.atom)
const oldValue = values[index]
newValue = adm.enhancer(newValue, oldValue)
const changed = newValue !== oldValue
if (changed) {
values[index] = newValue
// notifyArrayChildUpdate 方法内会调用 atom 实例的 reportChanged 方法发送变化通知
adm.notifyArrayChildUpdate(index, newValue, oldValue)
}
} else if (index === values.length) {
// 添加一个元素
adm.spliceWithArray(index, 0, [newValue])
} else {
// 抛出错误...
}
}

Mobx 中数组的 length 属性也是可观察的,原理也是一样的,都是利用原型链,在 Observable.prototype 上定义有 length 访问器属性,这里不再赘述。

为了得到类似原生数组的使用体验,ObservableArray 实现了所有原生数组的方法。来看看具体的实现方式。

对于 map, slice, toString 等不修改原数组的方法,只是在调用之前发送被观察通知:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
;[
"every",
"filter",
"forEach",
"indexOf",
"join",
"lastIndexOf",
"map",
"reduce",
"reduceRight",
"slice",
"some",
"toString",
"toLocaleString"
].forEach(funcName => {
const baseFunc = Array.prototype[funcName]
addHiddenProp(ObservableArray.prototype, funcName, function() {
// 先调用 peek 方法,发送被观察通知
// 然后在返回的数组项上调用方法
return baseFunc.apply(this.peek(), arguments)
})
})

// ObservableArray.prototype.peek
peek(): T[] {
// 发送被观察通知
this.$mobx.atom.reportObserved()
// 返回原始数组项供方法使用
return this.$mobx.dehanceValues(this.$mobx.values)
}

对于会修改原数组的方法,调用 admin 对象上的方法进行操作,比如:

1
2
3
4
5
6
7
// ObservableArray.prototype.push
push(...items: T[]): number {
const adm = this.$mobx
// spliceWithArray 方法内部会发送变化通知
adm.spliceWithArray(adm.values.length, 0, items)
return adm.values.length
}

Map

与数组的处理类似,Mobx 也实现了一个 ObservableMap 类,不过只支持字符串、数字或布尔值作为键。ObservableMap 在可观察对象的基础上,还要使键的增删可观察。它可以看做两个可观察映射和一个可观察数组的组合:

ObservableMap示意

这里的可观察映射指的是属性值可观察而对象属性不可观察,相当于 ObservableObjectAdministration 的 values 属性。_data 存放键到可观察值的映射,_hasMap 存放键是否存在的映射。_keys 属性是一个 ObservableArray 实例,存放所有的键,从而使键的增删可观察。

三者配合从而得到一个可观察粒度比对象更细的 Map。例如一个使用了 get 方法的 Derivation 只观察该键值对的变化,而不会观察其它键值对的设置和增删:

1
2
3
4
5
6
7
8
9
10
const map = observable(new Map());

autorun(() => {
console.log(map.get('key'));
});

map.set('key', 'value'); // 新增 key-value 键值对,输出 value
map.set('key', 'anotherValue'); // 修改为 key-anotherValue,输出 anotherValue
map.set('prop', 'value'); // 不输出
map.delete('prop'); // 不输出

来看 get 方法的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
get(key: string): V | undefined {
key = "" + key
// 如果存在该键值对,触发该值的 reportObserved
if (this.has(key)) return this.dehanceValue(this._data[key]!.get())
return this.dehanceValue(undefined)
}

has(key: string): boolean {
if (!this.isValidKey(key)) return false
key = "" + key
// _hasMap 中对应值(observable(bool))发送观察通知
if (this._hasMap[key]) return this._hasMap[key].get()
return this._updateHasMapEntry(key, false).get()
}

也就是说,这个 Derivation 只收集了这个键对应的值和表示是否存在该键的布尔值作为依赖,因而只对该键的值变化和该键值对的增删作出响应。

再比如使用了 keys 方法的 Derivation 只观察键值对的增删,而使用了 values 方法的 Derivation 同时观察键值对的增删和值的变化:

1
2
3
4
5
6
7
8
9
10
keys(): string[] & Iterator<string> {
// _keys.slice 触发 _keys 的被观察通知
return arrayAsIterator(this._keys.slice())
}

values(): V[] & Iterator<V> {
// _keys.map 触发 _keys 的被观察通知
// 依次调用 this.get 触发每一个可观察值的被观察通知
return (arrayAsIterator as any)(this._keys.map(this.get, this))
}

发送被观察通知

接下来看看 reportObserved 是如何发送通知的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function reportObserved(observable: IObservable) {
// 从全局状态中取出当前正在跟踪的 Derivation
const derivation = globalState.trackingDerivation
if (derivation !== null) {
/**
* 一处简单的性能优化,如果是同一次 Derivation 执行访问到 Observable
* 直接忽略
*/
if (derivation.runId !== observable.lastAccessedBy) {
observable.lastAccessedBy = derivation.runId
// 将 Observable 加入衍生的 newObserving 数组中,
// 同时更新 unboundDepsCount(新增的依赖数量)
derivation.newObserving![derivation.unboundDepsCount++] = observable
}

// 如果 Observable 不再有观察者时,将他加入到一个全局队列中待处理
} else if (observable.observers.length === 0) {
queueForUnobservation(observable)
}
}

reportObserved 函数从全局状态中取出当前正在执行的 Derivation,把 Observable 加入其 newObserving 数组中。Derivation 执行完后,会比较新旧 observing 数组,重新计算出依赖(这部分内容可以参看第四篇)。

发送变化通知

再来看 propageteChanged 是如何发送变化通知的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function propagateChanged(observable: IObservable) {
// 如果该 Observable 的所有观察者都已经处于过期状态,就没有必要发送过期通知了
if (observable.lowestObserverState === IDerivationState.STALE) return
// 标记所有观察者都处于过期状态
observable.lowestObserverState = IDerivationState.STALE

// 遍历所有观察者,将它们的 dependenciesState 标志设为过期,表示有依赖过期,需要重新计算
const observers = observable.observers
let i = observers.length
while (i--) {
const d = observers[i]
// 如果依赖由最新变为过期,调用该观察者的 onBecomeStale 方法
if (d.dependenciesState === IDerivationState.UP_TO_DATE) d.onBecomeStale()
d.dependenciesState = IDerivationState.STALE
}
}

可以看到,propagateChanged 只是将 Observable 和它的观察者们的标志设为了过期,并没有实际执行任何的重新计算。在一个 Derivation 的依赖由最新变为过期时,会调用它的 onBecomeStale 方法。

Reaction 的 onBecomeStale 方法只是简单的调用了 schedule 方法,将该 Reaction 的更新「加入了一个计划表内」:

1
2
3
4
5
6
7
8
9
10
11
12
13
onBecomeStale() {
this.schedule()
}

schedule() {
// 已经在重新计算的计划表内,直接返回
if (!this._isScheduled) {
this._isScheduled = true
// 该 Reaction 加入全局的待重新计算数组中
globalState.pendingReactions.push(this)
runReactions()
}
}

这张计划表实际上就是一个全局的数组,放置的是当前批次需要重新执行的所有 Reaction。
紧接着调用了 runReactions 函数:

1
2
3
4
5
function runReactions() {
// 此时处于事务中,inBatch > 0,会直接返回
if (globalState.inBatch > 0 || globalState.isRunningReactions) return
reactionScheduler(runReactionsHelper)
}

这种情形下 runReactions 实际不会执行 Reaction 的重新计算,因为此时至少会处于一个事务当中,即 Observable 在调用 reportChanged 时所开始的事务。

第四篇中我们将会看到,Derivation 是如何根据 Observalbe 的通知,动态更新自身的依赖或执行重新计算的。