knockout原始碼分析之computed(依賴屬性)

小龍女先生發表於2016-07-25

一、序列圖

knockout原始碼分析之computed(依賴屬性)

二、主要程式碼檔案

1、dependentObservable.js:主要包含ko.computed相關方法的處理
2、dependencyDetection.js:主要包含依賴的監控上下文物件。

三、主要邏輯

1、首先為某個屬性定義 一個computed物件,如下原始碼:

var vModel = function(){
        this.fName = ko.observable('fName'),
        this.lName= ko.observable('lName'),
        this.name= ko.computed(function () { //監控依賴物件
            return this.fName() + '-' + this.lName();
        },this);
    };
2、當程式碼在執行ko.computed方法,求值方法被作為引數傳入,並賦值給options的read屬性
3、建立一個state字面量物件,其中包含read、write屬性,如下程式碼:
var state = {
        latestValue: undefined,
        isStale: true,
        isBeingEvaluated: false,
        suppressDisposalUntilDisposeWhenReturnsFalse: false,
        isDisposed: false,
        pure: false,
        isSleeping: false,
        readFunction: options["read"],
        evaluatorFunctionTarget: evaluatorFunctionTarget || options["owner"],
        disposeWhenNodeIsRemoved: options["disposeWhenNodeIsRemoved"] || options.disposeWhenNodeIsRemoved || null,
        disposeWhen: options["disposeWhen"] || options.disposeWhen,
        domNodeDisposalCallback: null,
        dependencyTracking: {},
        dependenciesCount: 0,
        evaluationTimeoutInstance: null
    };
4、生成computedObservable物件(function),然後將state附加到_state屬性上,則擴充套件為釋出/訂閱物件。
5、擴充套件computedFn所有方法和屬性到computedObservable物件上
// Inherit from 'subscribable'
    if (!ko.utils.canSetPrototype) {
        // 'subscribable' won't be on the prototype chain unless we put it there directly
        ko.utils.extend(computedObservable, ko.subscribable['fn']);
    }
    ko.subscribable['fn'].init(computedObservable); //執行釋出/訂閱物件的init方法,用於初始化釋出/訂閱物件。

    // Inherit from 'computed'
    ko.utils.setPrototypeOfOrExtend(computedObservable, computedFn);
6、然後執行computedObservable的evaluateImmediate方法,此方法中最重的三點:
   6.1、在evaluateImmediate_CallReadWithDependencyDetection方法中,建立了依賴監控物件,並新增到依賴監控上下文中
var isInitial = state.pure ? undefined : !state.dependenciesCount,   // If we're evaluating when there are no previous dependencies, it must be the first time
            dependencyDetectionContext = {
                computedObservable: computedObservable,
                disposalCandidates: state.dependencyTracking,
                disposalCount: state.dependenciesCount
            };

        ko.dependencyDetection.begin({
            callbackTarget: dependencyDetectionContext,
            callback: computedBeginDependencyDetectionCallback,
            computed: computedObservable,
            isInitial: isInitial
        });
    6.2、然後呼叫evaluateImmediate_CallReadThenEndDependencyDetection方法,引數傳遞的state(在ko.computed方法中定義的)、dependencyDetectionContext(依賴監控物件)
    6.3、其中用到了try catch finall方式,確保ko.dependencyDetection.end方法的執行
try {
            var readFunction = state.readFunction;
            return state.evaluatorFunctionTarget ? readFunction.call(state.evaluatorFunctionTarget) : readFunction();
        } finally {
            ko.dependencyDetection.end();

            // For each subscription no longer being used, remove it from the active subscriptions list and dispose it
            if (dependencyDetectionContext.disposalCount && !state.isSleeping) {
                ko.utils.objectForEach(dependencyDetectionContext.disposalCandidates, computedDisposeDependencyCallback);
            }

            state.isStale = false;
        }
7、在執行ko.computed的readFunction方法時,其中就執行了ko.observable方法(執行的是read),這時就會去呼叫ko.dependencyDetection.registerDependency方法(引數為此函式物件)
function observable() {
        if (arguments.length > 0) {
            // Write

            // Ignore writes if the value hasn't changed
            if (observable.isDifferent(observable[observableLatestValue], arguments[0])) {
                observable.valueWillMutate();
                observable[observableLatestValue] = arguments[0];
                observable.valueHasMutated();
            }
            return this; // Permits chained assignments
        }
        else {
            debugger;
            // Read
            ko.dependencyDetection.registerDependency(observable); //執行依賴
            return observable[observableLatestValue];
        }
    }
8、在ko.dependencyDetection中的registerDependency方法內,首先會判斷ko.observable是否為訂閱物件,如果是則執行begin加入的callbak函式.
registerDependency: function (subscribable) { //注入到相關依賴屬性
            if (currentFrame) {
                if (!ko.isSubscribable(subscribable))
                    throw new Error("Only subscribable things can act as dependencies");
                currentFrame.callback.call(currentFrame.callbackTarget, subscribable, subscribable._id || (subscribable._id = getId()));
            }
        }
9、執行evaluateImmediate方法後,然後註冊Dom移除回撥事件。
if (state.disposeWhenNodeIsRemoved && computedObservable.isActive()) {
        ko.utils.domNodeDisposal.addDisposeCallback(state.disposeWhenNodeIsRemoved, state.domNodeDisposalCallback = function () {
            computedObservable.dispose();
        });
    }
10、返回computedObservable物件

四、補充說明

1、ko.dependencyDetection中有ignore方法,他主要實現的是一個非同步鎖,讓callbcak處於鎖的狀態執行

ignore: function (callback, callbackTarget, callbackArgs) { //按順序s執行依賴,但不觸發訂閱。
            try {
                begin();
                return callback.apply(callbackTarget, callbackArgs || []);
            } finally {
                end();
            }
        }
2、ko.computed 與 ko.dependentObservable是相同的。

相關文章