Mobx 源码分析 - array

通过以下方法将数组变成可观察数组。

@observable person = []
person = observable.array([])
person = observable([])
1
2
3

前言

person = observable([])
// 等同于
person = observable.array([])
1
2
3

这两种调用方式是一样的,只不过直接调用 observable,会在内部判断参数类型,然后再调用 observable.array

@observable person = []
1

本质上其实还是通过 observable.array 方式调用,具体可以参考之前写的文章Mobx 源码分析 - 第一阶段汇总

observable.array

调用 observable.array 方法其实就是实例化 ObservableArray

array: function (initialValues, options) {
    if (arguments.length > 2)
        incorrectlyUsedAsDecorator("array");
    var o = asCreateObservableOptions(options);
    return new ObservableArray(initialValues, getEnhancerFromOptions(o), o.name);
}
1
2
3
4
5
6

ObservableArray 继承原生数组,且拥有原生数组方法,并在原型上增加 $mobx 对象,对象为 ObservableArrayAdministration 实例。

如果数组有初始值,则修改全局状态 allowStateChanges,并调用 spliceWithArray 方法,调用完成后,把之前的全局状态 allowStateChanges 值重新赋值回去。

if (initialValues && initialValues.length) {
    const prev = allowStateChangesStart(true)
    this.spliceWithArray(0, 0, initialValues)
    allowStateChangesEnd(prev)
}
1
2
3
4
5

spliceWithArray 方法本质上是调用 $mobx 上方法,前面说过 $mobxObservableArrayAdministration 实例,所以也就相当于调用 ObservableArrayAdministrationspliceWithArray 方法。

spliceWithArray(index: number, deleteCount?: number, newItems?: T[]): T[] {
    return this.$mobx.spliceWithArray(index, deleteCount, newItems)
}
1
2
3

为什么 console 中查看可观察的数组显示的长度为 1000

mobxObservableArray 原型上初始化 1000 个键值对,键是从 0 - 1000,值为属性描述符。

function createArrayEntryDescriptor(index: number) {
    return {
        enumerable: false,
        configurable: false,
        get: function() {
            return this.get(index)
        },
        set: function(value) {
            this.set(index, value)
        }
    }
}

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

export function reserveArrayBuffer(max: number) {
    for (let index = OBSERVABLE_ARRAY_BUFFER_SIZE; index < max; index++)
        createArrayBufferItem(index)
    OBSERVABLE_ARRAY_BUFFER_SIZE = max
}

reserveArrayBuffer(1000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

ObservableArrayAdministration

实例化 ObservableArrayAdministration 会把 ObservableArray 的原型作为 this.array 的值,并把实例化 Atom 赋值给 this.atom

spliceWithArray

取有效的起始位置和删除个数。

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))
1
2
3
4
5
6
7
8

如果属性上有拦截器 intercept,则会先调用传递的拦截器函数,函数返回值会作为数组修改的依据。

const change = interceptChange<IArrayWillSplice<T>>(this as any, {
    object: this.array,
    type: "splice",
    index,
    removedCount: deleteCount,
    added: newItems
})
if (!change) return EMPTY_ARRAY
deleteCount = change.removedCount
newItems = change.added
1
2
3
4
5
6
7
8
9
10

对于数组中每一项调用 enhancer 方法,然后更新数组的长度。如果删除个数与新增个数都不为 0,则发出更改事件,并进行衍生,最后返回 values 值。

// 更改数组长度
this.updateArrayLength(length, lengthDelta)
// 修改数组,并给将新数组赋值给 this.values,删除项赋值给 res
const res = this.spliceItemsIntoValues(index, deleteCount, newItems)
// 发送更改事件
if (deleteCount !== 0 || newItems.length !== 0) this.notifyArraySplice(index, newItems, res)
// 返回删除后每一项组成的数组
return this.dehanceValues(res)
1
2
3
4
5
6
7
8

get

当我们打印 person,会打印出一个 ObservableArray 实例,但是数组的内容并不会直接显示,这是因为数组每一项都被 getset 截取,每当访问其中一项,都会调用 ObservableArrayget 方法,并把当前项的索引 index 当做参数传递进去。

当我们使用了数组其中一项时,当前对象会通过调用 reportObserved,向外界发出监听此对象指令,并返回当前项的值。

get(index: number): T | undefined {
    const impl = <ObservableArrayAdministration<any>>this.$mobx
    if (impl) {
        if (index < impl.values.length) {
            impl.atom.reportObserved()
            return impl.dehanceValue(impl.values[index])
        }
        console.warn(
            `[mobx.array] Attempt to read an array index (${index}) that is out of bounds (${
                impl.values.length
            }). Please check length first. Out of bound indices will not be tracked by MobX`
        )
    }
    return undefined
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
最后更新: 10/10/2019, 3:41:28 PM