mobx原始碼解讀—— autorun 與 observable

勵步前端團隊發表於2021-12-04
第一次閱讀原始碼,可能有理解的不太正確的地方希望大佬們能幫我糾正。開始看的是6,後來看到observable發現和5的差距還是有一點的,所以在所以“autorun”的部分可能會有6的原始碼,但差距並不大。

1.mobx的基本概念

Observable 被觀察者

Observer 觀察

Reaction 響應

var student = mobx.observable({
    name: '張三',
});

mobx.autorun(() => {
    console.log('張三的名字:', student.name);
});

2.mobx的原理

1.在響應式函式中(如以上autorun中通常會訪問一個或多個observable物件),

  • 1)autorun首先建立一個Reaction型別的例項物件reaction,通過引數track一個響應式函式的回撥函式。
  • 2)然後執行reaction.schedule_方法,執行回撥函式,回撥函式中呼叫被觀察者observable.get方法,觸發reportObserved方法。
  • 3)reportObserved方法中會將observavle物件收集到globalState.trackingDerivation.newObserving_佇列中(globalState.trackingDerivation此時等同於reaction物件)
  • 4)處理reaction和observable的依賴關係,遍歷reaction.newObserving_屬性,在newObserving_佇列中的每一個observable.observers_屬性中新增當前reaction物件。

2.被觀察者observable的value發生變化,呼叫observable物件set方法,觸發propagateChange方法。propagateChange方法中,遍歷observable.observers_屬性依次執行reaction.onBecomeStale方法,再次將以上的2)3)4)執行一遍。

3.原始碼解讀–autorun

以下為刪減後的程式碼

3.1 autorun

export function autorun(
    view: (r: IReactionPublic) => any,// autoruan函式的回撥函式
    opts: IAutorunOptions = EMPTY_OBJECT
): IReactionDisposer {
    
    const name: string = "Autorun"
    const runSync = !opts.scheduler && !opts.delay
    
    // 首先建立一個Reaction型別的物件 主要功能是用來控制任務的執行
    let reaction = new Reaction(
        name,
        function (this: Reaction) {
            this.track(reactionRunner)
        },
        opts.onError,
        opts.requiresObservable
    )

    function reactionRunner() { view(reaction) } // view即autorun函式的回撥

    reaction.schedule_() // 立即執行一次部署    
    
    return reaction.getDisposer_() // 用於在執行期間清理 autorun
}

從上邊的原始碼可以看出autorun主要做了一下三個動作

  • 1)建立一個Reaction型別的物件 主要功能是用來控制任務的執行
  • 2)將view即auto的回撥函式,分配給reaction.track
  • 3)立即執行一次部署 ,此時你應該理解文件中所說的“當使用autorun時,所提供的函式總是立即被觸發”

3.2 reaction.schedule_的原始碼

schedule_() {
    if (!this.isScheduled_) {
        this.isScheduled_ = true
        globalState.pendingReactions.push(this) // 當前的reaction物件入列
        runReactions() // 佇列中的所有reaction物件執行runReaction_方法
    }
}

function runReactionsHelper() {
   let remainingReactions = allReactions.splice(0)
   for (let i = 0, l = remainingReactions.length; i < l; i++)
        remainingReactions[i].runReaction_()
}

schedule_方法做了兩件事

  • 1)當前的reaction物件入列
  • 2)佇列中的所有reaction物件執行runReaction_方法

3.3 runReaction_原始碼

runReaction_() {
    startBatch() // 開啟一層事務
    this.isScheduled_ = false
   
    if (shouldCompute(this)) {// derivation.dependenciesState_預設為-1 (未跟蹤) 
       this.onInvalidate_()
    }
    endBatch() // 關閉一層事務 startBatch和endBatch總是成對出現
}

翻看上邊的程式碼可以發現onInvalidate_是在初始化Reaction時傳入建構函式的,實際上時呼叫了reaction.track方法

track(fn: () => void) {
    startBatch()
    ...
    const result = trackDerivedFunction(this, fn, undefined) // 執行任務 更新依賴
    ...
    endBatch()
}

3.4 track方法主要是呼叫了trackDerivedFunction

export function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
    ...
    globalState.trackingDerivation = derivation  // 將derivation(此處等同於reaction物件)掛載到全域性變數 這樣其他成員也可訪問此derivation
    ...
    // 執行reaction傳遞的的回撥方法,翻看程式碼可以看出執行的是autoran函式的回撥方法
    // 回撥中一般會呼叫一或多個observable物件,觸發observable.get方法,再觸發reportObserved方法
    let result = f.call(context)
    ...
    globalState.trackingDerivation = prevTracking
    bindDependencies(derivation) // 更新observable和raction的依賴關係
    return result
}

執行因為autorun回撥用到了student.name變數,這裡的"."其實就是get操作;一旦設計到get操作,監督這個name的屬性的觀察員就會執行reportObserved方法(後邊介紹Oobservable時候會重點介紹這裡)。

3.5 reportObserved原始碼

export function reportObserved(observable: IObservable): boolean {
    ...
    const derivation = globalState.trackingDerivation
    if (derivation !== null) {

        if (derivation.runId_ !== observable.lastAccessedBy_) {
            
             // 更被觀察者的lastAccessedBy_屬性(事務id),這個是為了避免重複操作
            observable.lastAccessedBy_ = derivation.runId_ 
            
            // 更新derivation(此處為reaction)的newObserving屬性,將被觀察者加入該佇列中
            // 後續derivation和observable更新依賴關係就靠這個屬性
            derivation.newObserving_![derivation.unboundDepsCount_++] = observable 

            ...
        }
        return true
    } else if (observable.observers_.size === 0 && globalState.inBatch > 0) {
        queueForUnobservation(observable)
    }

    return false
}

上邊的程式碼,我們主要關注影響derivation的操作

  • 1)更新observable的lastAccessedBy_屬性(事務id),這個是為了避免重複操作。
  • 2)更新derivation(此處為reaction)的newObserving屬性,將observable加入該佇列中,後續derivation和observable更新依賴關係就靠這個屬性

隨後autorun的任務執行完成後,derivation就開始著手更新和被觀察者observable的依賴關係

3.6 bindDependencies原始碼

function bindDependencies(derivation: IDerivation) {
    const prevObserving = derivation.observing_
     
    // derivation.newObserving_為derivation依賴的observable物件的佇列
    const observing = (derivation.observing_ = derivation.newObserving_!)
    let lowestNewObservingDerivationState = IDerivationState_.UP_TO_DATE_ // 預設為0

    let i0 = 0,
        l = derivation.unboundDepsCount_
    for (let i = 0; i < l; i++) {

        /**
         * 以下是一個去重的過程 
         * observable.diffValue_預設是0
         * 迴圈時候置為1,因為observing為Observable型別的物件陣列,所以不同位置上相同的值的diffValue_都會變成1
         * 在遍歷到重複項後就不會進入下邊的判斷,i0就不會++
         * 遍歷到非重複項(diffValue_為0的項),則直接將此項填充到i0對應的位置上
         * 這樣陣列迴圈完畢,i0即非重複項的數量,observing.length = i0即刪除掉了多餘項
         */
        
        const dep = observing[i]
        if (dep.diffValue_ === 0) {
            dep.diffValue_ = 1
            if (i0 !== i) observing[i0] = dep
            i0++
        }
    }
    observing.length = i0

    derivation.newObserving_ = null // newObserving 置空

    /**
     * prevObserving中和observing中存在的均為observable物件 
     * 此時如果在上邊的迴圈完成後 observing存在的observable物件的diffValue_均為1
     * 在prevObserving佇列如果是diffValue_仍然為0,表示當前derivation已經不依賴此observable物件
     */
    l = prevObserving.length
    while (l--) {
        const dep = prevObserving[l];
        if (dep.diffValue_ === 0) {
            // 將當前derivation已不再依賴此observable物件,將其從observable.observers_中的deleted掉
            removeObserver(dep, derivation)  
        }
        dep.diffValue_ = 0 // 將prevObserving佇列中的observable的diffValue_均置為0
    }

    
    while (i0--) {
        const dep = observing[i0]
        
        // observing仍然為1的說明此observable物件不在prevObserving佇列中 
        if (dep.diffValue_ === 1) {
            dep.diffValue_ = 0
            // 在observable.observers_中新增當前的derivation物件
            addObserver(dep, derivation) 
        }
    }
    // 通過以上的3次迴圈,將derivation.observing更新為最新的依賴(並去重),
    // 並在已經不依賴的observable物件的observers_中delete當前的derivation物件
    // 在新建立起的依賴的observable物件的observers_中add當前的derivation物件
}

響應被觀察者observable物件的value發生變化
上邊提及,一旦observable的value發生變化,就會觸發observable.get方法,然後觸發propagateChange方法,propageateChange原始碼如下

export function propagateChanged(observable: IObservable) {
    ...
    observable.observers_.forEach(d => {
        ...
            d.onBecomeStale_()
        ...
    })
    
}

observable.observers_儲存的是,與observable物件有依賴關係的derivation物件,在propagateChanged方法中,遍歷observers_執行derivation物件的onBecomeStale_方法,我們來看一下onBecomeStale_的原始碼

3.7 onBecomeStale_的原始碼

onBecomeStale_() {
    this.schedule_()
}

this.schedule_是不是很熟悉,翻一下上邊的程式碼,發現是在autorun函式中建立reaction對像的時候呼叫了reaction.schedule_()。所以這下明白propagateChanged呼叫onBecomeStale_是讓reaction再次執行一次之前的部署操作(也就是執行autorun的回撥,處理依賴關係);

4.接下來開始看observable(被觀察者)部分

4.1 observable的別名createObservable

export const observable: IObservableFactory &
    IObservableFactories & {
        enhancer: IEnhancer<any>
    } = createObservable as any // observable的別名createObservable

// 將observableFactories的屬性複製一份給observable
Object.keys(observableFactories).forEach(name => (observable[name] = observableFactories[name]))
  • 1)首先 observable 是函式函式同 createObservable。
  • 2)observable複製了observableFactories的屬性。
function createObservable(v: any, arg2?: any, arg3?: any) {
    // @observable someProp;
    if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
        return deepDecorator.apply(null, arguments as any)
    }

    // it is an observable already, done
    if (isObservable(v)) return v

    // something that can be converted and mutated?
    const res = isPlainObject(v)
        ? observable.object(v, arg2, arg3)
        : Array.isArray(v)
        ? observable.array(v, arg2)
        : isES6Map(v)
        ? observable.map(v, arg2)
        : isES6Set(v)
        ? observable.set(v, arg2)
        : v

    // this value could be converted to a new observable data structure, return it
    if (res !== v) return res
}

createObservable方法起到了轉發的作用,將傳入的物件轉發給具體的轉換函式。
簡單分析一下具體的轉化模式

  • 1)arguments[1] === “string” || typeof arguments[1] === “symbol” 採用的是裝飾器@observable,裝飾器的引數(target,prop,descriptor)其中arguments[1] 也就是prop為屬性名稱為字串型別
  • 2)isObservable(v) 已經轉換為觀察值了不需要再轉換
  • 3)observable.object、observable.array、observable.map、observable.set根據傳入引數的型別分別呼叫具體的轉換方法
  • 4)針對原始型別提示使用者建議使用observable.box方法

4.2 observable.box

observable.box在文件中是這樣介紹的。
下載 1.jpeg
observable.box把普通的值轉換成可觀察的值,如下例。

const name = observable.box("張三");

console.log(name.get());
// 輸出 '張三'

name.observe(function(change) {
    console.log(change.oldValue, "->", change.newValue);
});

name.set("李四");
// 輸出 '張三 -> 李四'

observable.box retrun 一個ObservableValue型別的對像。

box<T = any>(value?: T, options?: CreateObservableOptions): IObservableValue<T> {
    const o = asCreateObservableOptions(options) // 格式化入參
    // ObservableValue的擁有方法get set observe intercept...
    return new ObservableValue(value, getEnhancerFromOptions(o), o.name, true, o.equals)
},

案例中的“name.set(“李四”)”,就是呼叫了ObservableValue的set方法。一會再介紹ObservableValue的時候會重點說下。

4.3 核心類 ObservableValue

ObservableValue 繼承了 Atom原子類,先梳理一下Atom和ObservableValue和有什麼主要能力。

Atom
 
public reportObserved(): boolean {
    return reportObserved(this)
}

public reportChanged() {
    startBatch()
    propagateChanged(this)
   endBatch()
}
ObservableValue

public set(newValue: T) {
    const oldValue = this.value
    newValue = this.prepareNewValue(newValue) as any
    if (newValue !== globalState.UNCHANGED) {
        const oldValue = this.value
        this.value = newValue
        this.reportChanged()
        ...
    }
}
public get(): T {
    this.reportObserved()
    return this.dehanceValue(this.value)
}

intercept
observe

其中reportObserved、propagateChanged在梳理autorun的時候介紹過。

  • 1)reportObserved:呼叫觀察值是用於更新derivation和observable的依賴關係。
  • 2)propagateChanged:觀察值改變時,observable物件的observers中儲存的derivation,執行onBecomeStale方法,重新執行部署操作。
  • 3)Observablevalue的set 修改value同時呼叫Atom的reportChanged方法觸發propagateChanged。
  • 4)Observablevalue的get 獲取value值的同時呼叫Atom的reportObserved方法觸發reportObserved。

所以上邊案例中“name.set(“李四”);”會觸發propagateChanged方法,會執行有依賴關係的 derivation 重新執行部署操作

接下來看一下new ObservableValue的時候幹了什麼?

constructor(
    value: T,
    public enhancer: IEnhancer<T>,
    public name = "ObservableValue@" + getNextId(),
    notifySpy = true,
    private equals: IEqualsComparer<any> = comparer.default
) {
    ...
    this.value = enhancer(value, undefined, name)
}

ObservableValue的建構函式中呼叫enhancer對value進行了處理,enhancer是通過引數是建立ObservableValue型別物件是傳遞的引數getEnhancerFromOptions(o)。getEnhancerFromOptions預設返回的是deepEnhancer。

function getEnhancerFromOptions(options: CreateObservableOptions): IEnhancer<any> {
    return options.defaultDecorator
        ? options.defaultDecorator.enhancer
        : options.deep === false
        ? referenceEnhancer
        : deepEnhancer
}

gdeepEnhancer主要內容如下。

export 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, undefined, { name })
    if (isES6Map(v)) return observable.map(v, { name })
    if (isES6Set(v)) return observable.set(v, { name })
    return v
}

這個deepEnhancer是不是看上去有點眼熟,往上翻一下可以看出他和createObservable 函式十分相似,起到了轉發的作用,將傳入的物件轉發給具體的轉換函式。所以要理解observable我門主要就是要了解這些轉換函式。接下來我們主要分析observable.object。

4.4 observable.object

object<T = any>(
        props: T,
        decorators?: { [K in keyof T]: Function },
        options?: CreateObservableOptions
    ): T & IObservableObject {
  
    const o = asCreateObservableOptions(options)
    if (o.proxy === false) {
        return extendObservable({}, props, decorators, o) as any
    } else {
        const defaultDecorator = getDefaultDecoratorFromObjectOptions(o)
        const base = extendObservable({}, undefined, undefined, o) as any
        const proxy = createDynamicObservableObject(base)
        extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator)
        return proxy
    }
}

o.proxy為true的時候只是多了一步Proxy,其餘的工作基本相似,所以主要關注extendObservable方法就可以了。

extendObservable中調主要用了getDefaultDecoratorFromObjectOptions、asObservableObject、extendObservableObjectWithProperties方法。因為getDefaultDecoratorFromObjectOptions與extendObservableObjectWithProperties有關聯,所以先來看asObservableObject,再看另外兩個方法。

4.5 extendObservable

export function extendObservable<A extends Object, B extends Object>(
    target: A,
    properties?: B,
    decorators?: { [K in keyof B]?: Function },
    options?: CreateObservableOptions
): A & B {
    options = asCreateObservableOptions(options)
    const defaultDecorator = getDefaultDecoratorFromObjectOptions(options) // 預設返回deepDecorator裝飾器
    asObservableObject(target, options.name, defaultDecorator.enhancer) // make sure object is observable, even without initial props
    if (properties)
        extendObservableObjectWithProperties(target, properties, decorators, defaultDecorator)
    return target as any
}

4.6 asObservableObject

asObservableObject方法:

  • 1)建立一個物件amd為ObservableObjectAdministration類的例項。
  • 1)amd賦值給target[$mobx]
  • 2)返回amd;
export function asObservableObject(
    target: any,
    name: PropertyKey = "",
    defaultEnhancer: IEnhancer<any> = deepEnhancer
): ObservableObjectAdministration {
    const adm = new ObservableObjectAdministration(
        target,
        new Map(),
        stringifyKey(name),
        defaultEnhancer
    )
    addHiddenProp(target, $mobx, adm)
    return adm
}

4.7 extendObservableObjectWithProperties

extendObservableObjectWithProperties:迴圈原始物件,對每一個屬性值經過都decorator函式處理(decorators方法即通過getDefaultDecoratorFromObjectOptions方法獲取的預設為deepDecorator,所以一回直接看deepDecorator)

export function extendObservableObjectWithProperties(
    target,
    properties, // 原物件
    decorators,
    defaultDecorator
) {
    startBatch()
    const keys = ownKeys(properties)
    
    // 迴圈原物件
    for (const key of keys) {
        const descriptor = Object.getOwnPropertyDescriptor(properties, key)!
        const decorator =
            decorators && key in decorators
                ? decorators[key]
                : descriptor.get
                ? computedDecorator
                : defaultDecorator
        const resultDescriptor = decorator!(target, key, descriptor, true) // 經過裝飾器處理
        if (
            resultDescriptor // otherwise, assume already applied, due to `applyToInstance`
        )
            Object.defineProperty(target, key, resultDescriptor)
    }

    endBatch()
}

4.8 decorator

decorator預設為deepDecorator,我們來看一下它都幹了什麼。

export function createDecoratorForEnhancer(enhancer: IEnhancer<any>): IObservableDecorator {
    const decorator = createPropDecorator(
        true,
        (
            target: any,
            propertyName: PropertyKey,
            descriptor: BabelDescriptor | undefined,
            _decoratorTarget,
            decoratorArgs: any[]
        ) => {

            const initialValue = descriptor
                ? descriptor.initializer
                    ? descriptor.initializer.call(target)
                    : descriptor.value
                : undefined
                // 呼叫target[$mobx].addObservableProp方法
            asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer)
        }
    )
    const res: any = decorator
    res.enhancer = enhancer
    return res
}

4.9 addObservableProp方法

decorator中呼叫了target[$mobx].addObservableProp方法

addObservableProp(
    propName: PropertyKey,
    newValue,
    enhancer: IEnhancer<any> = this.defaultEnhancer
) {
    const { target } = this
    if (hasInterceptors(this)) {
        // 攔截處理
        const change = interceptChange<IObjectWillChange>(this, {
            object: this.proxy || target,
            name: propName,
            type: "add",
            newValue
        })
        if (!change) return // 攔截器返回空的時候不需要重新忽略此次修改。
        newValue = (change as any).newValue
    }
    // newValue轉換成ObservableValue型別
    const observable = new ObservableValue(
        newValue,
        enhancer,
        `${this.name}.${stringifyKey(propName)}`,
        false
    )
    this.values.set(propName, observable) // 儲存
    newValue = (observable as any).value 
    
    // generateObservablePropConfig方法返回以下描述符
    // { ..., get() { return this[$mobx].read(propName)  }, set(v) { this[$mobx].write(propName, v) } }
    Object.defineProperty(target, propName, generateObservablePropConfig(propName)) // target生成propName屬性
    const notify = hasListeners(this)
    const change = {
          type: "add",
          object: this.proxy || this.target,
          name: propName,
          newValue
      }
    this.keysAtom.reportChanged() // this.keysAtom即Atom的例項
}

addObservableProp方法

  • 1)呼叫ObservableValue類將newValue轉換為可觀察值(還記不記得上邊ObservableValue呼叫通過enhancer呼叫了observable.object方法嗎。現在可以看出observable.object方法中在迴圈物件的屬性時又呼叫了ObservableValue。通過這種遞迴的方式將物件的屬性轉換為可觀察值)
  • 2)將屬性key和observable存入target[$mobx].values中
  • 3)將原物件屬性值新增到target,並通過描述符中get和set都是直接呼叫this[mobx].read和this[mobx].write方法。
  • 4)呼叫原子類Atom的reportChanged,讓依賴此observable物件的derivation重新執行部署操作。

綜上extendObservableObjectWithProperties作用即迴圈原始物件,執行以上4步,實現了將原始物件的屬性代理到target上,並將值轉換到可觀察值,儲存在target[$mobx].values中。

4.10 read和write

read(key: PropertyKey) {
    return this.values.get(key)!.get()
}


 // observable.get方法
 public get(): T {
    this.reportObserved() // Atom下的reportObserved
    return this.dehanceValue(this.value)
}

read方法會根據屬性名稱從this.values中查詢,獲取到對應的observable物件再呼叫observable.get方法觸發reportObserved

write(key: PropertyKey, newValue) {
    const instance = this.target
    const observable = this.values.get(key)
    // intercept
    if (hasInterceptors(this)) {
        const change = interceptChange<IObjectWillChange>(this, {
            type: "update",
            object: this.proxy || instance,
            name: key,
            newValue
        })
        if (!change) return
        newValue = (change as any).newValue
    }
    newValue = (observable as any).prepareNewValue(newValue)
    if (newValue !== globalState.UNCHANGED) {
        (observable as ObservableValue<any>).setNewValue(newValue)
    }
}


// observable.prepareNewValue和observable.setNewValue方法
private prepareNewValue(newValue): T | IUNCHANGED {
    if (hasInterceptors(this)) {
        const change = interceptChange<IValueWillChange<T>>(this, {
            object: this,
            type: "update",
            newValue
        })
        if (!change) return globalState.UNCHANGED
        newValue = change.newValue
    }
    // apply modifier
    newValue = this.enhancer(newValue, this.value, this.name) // 呼叫enhancer轉換為可觀察模式
    return this.equals(this.value, newValue) ? globalState.UNCHANGED : newValue
}

setNewValue(newValue: T) {
    const oldValue = this.value
    this.value = newValue
    this.reportChanged()
    if (hasListeners(this)) {
        notifyListeners(this, {
            type: "update",
            object: this,
            newValue,
            oldValue
        })
    }
}

write方法

  • 1)呼叫observable.prepareNewValue方法將新的value進行轉換
  • 2)呼叫observable.setNewValue重新修改值
  • 3)觸發reportChanged方法。

4.11 綜上總結

var student = mobx.observable({
    name: '張三',
});

mobx通過observable方法用target代理了傳入的物件,賦值給student。
因此student的結構應該如下

在呼叫student.name時候觸發會呼叫get=>read=>observableValue.get=>reportObserved
修改的時候 set=>write=>observableValue.setNewValue=>reportChanged

現在基本可以理解observable是autoruan之間的關係了。

相關文章