vue數據初始化--initState

數據初始化

Vue 實例在創建的時候會運行一系列的初始化操做,而在這些初始化操做裏面,和數據綁定關聯最大的是 initState。vue

首先,來看一下他的代碼:segmentfault

function initState(vm) {
    vm._watchers = [];
    var opts = vm.$options;
    if(opts.props) {
        initProps(vm, opts.props); //初始化props
    }
    if(opts.methods) {
        initMethods(vm, opts.methods); //初始化methods
    }
    if(opts.data) {
        initData(vm); //初始化data
    } else {
        observe(vm._data = {}, true /* asRootData */ );
    }
    if(opts.computed) {
        initComputed(vm, opts.computed); //初始化computed
    }
    if(opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch); //初始化watch
    }
}

在這麼多的數據的初始化中,props、methods和data是比較簡單的(因此我就不詳細介紹了☺),而computed 和 watch則相對較難,邏輯較複雜,因此我下面主要講下computed 和 watch(如下代碼部分爲簡化後的)。數組

initState裏面主要是對vue實例中的 props, methods, data, computed 和 watch 數據進行初始化。緩存

在初始化props的時候(initProps),會遍歷props中的每一個屬性,而後進行類型驗證,數據監測等(提供爲props屬性賦值就拋出警告的鉤子函數)。異步

在初始化methods的時候(initMethods),主要是監測methods中的方法名是否合法。函數

在初始化data的時候(initData),會運行 observe 函數深度遍歷數據中的每個屬性,進行數據劫持。oop

在初始化computed的時候(initComputed),會監測數據是否已經存在data或props上,若是存在則拋出警告,不然調用defineComputed函數,監聽數據,爲組件中的屬性綁定getter及setter。若是computed中屬性的值是一個函數,則默認爲屬性的getter函數。此外屬性的值還能夠是一個對象,他只有三個有效字段set、get和cache,分別表示屬性的setter、getter和是否啓用緩存,其中get是必須的,cache默認爲true。this

function initComputed(vm, computed) {
    var watchers = vm._computedWatchers = Object.create(null);

    for(var key in computed) {
        var userDef = computed[key];
        var getter = typeof userDef === 'function' ? userDef : userDef.get;

        //建立一個計算屬性 watcher
        watchers[key] = new Watcher(
            vm,
            getter || noop,
            noop,
            computedWatcherOptions
        );

        if(!(key in vm)) {
            //若是定義的計算屬性不在組件實例上,對屬性進行數據劫持
            //defineComputed 很重要,下面咱們再說
            defineComputed(vm, key, userDef);
        } else {
            //若是定義的計算屬性在data和props有,拋出警告
        }
    }
}

在初始化watch的時候(initWatch),會調用vm.$watch函數爲watch中的屬性綁定setter回調(若是組件中沒有該屬性則不能成功監聽,屬性必須存在於props、data或computed中)。若是watch中屬性的值是一個函數,則默認爲屬性的setter回調函數,若是屬性的值是一個數組,則遍歷數組中的內容,分別爲屬性綁定回調,此外屬性的值還能夠是一個對象,此時,對象中的handler字段表明setter回調函數,immediate表明是否當即先去執行裏面的handler方法,deep表明是否深度監聽。lua

vm.$watch函數會直接使用Watcher構建觀察者對象。watch中屬性的值做爲watcher.cb存在,在觀察者update的時候,在watcher.run函數中執行。想了解這一過程能夠看我上一篇的 vue響應式系統--observe、watcher、dep中關於Watcher的介紹。prototype

function initWatch(vm, watch) {
    //遍歷watch,爲每個屬性建立偵聽器
    for(var key in watch) {
        var handler = watch[key];
        //若是屬性值是一個數組,則遍歷數組,爲屬性建立多個偵聽器
        //createWatcher函數中封裝了vm.$watch,會在vm.$watch中建立偵聽器
        if(Array.isArray(handler)) {
            for(var i = 0; i < handler.length; i++) {
                createWatcher(vm, key, handler[i]);
            }
        } else {
            //爲屬性建立偵聽器
            createWatcher(vm, key, handler);
        }
    }
}

function createWatcher(vm, expOrFn, handler, options) {
    //若是屬性值是一個對象,則取對象的handler屬性做爲回調
    if(isPlainObject(handler)) {
        options = handler;
        handler = handler.handler;
    }
    //若是屬性值是一個字符串,則從組件實例上尋找
    if(typeof handler === 'string') {
        handler = vm[handler];
    }
    //爲屬性建立偵聽器
    return vm.$watch(expOrFn, handler, options)
}

computed

computed本質是一個惰性求值的觀察者,具備緩存性,只有當依賴變化後,第一次訪問 computed 屬性,纔會計算新的值

下面將圍繞這一句話來作解釋。

上面代碼中提到過,當計算屬性中的數據存在與data和props中時,會被警告,也就是這種作法是錯誤的。因此通常的,咱們都會直接在計算屬性中聲明數據。仍是那個代碼片斷中,若是定義的計算屬性不在組件實例上,會運行defineComputed函數對數據進行數據劫持。下面咱們來看下defineComputed函數中作了什麼。

function defineComputed(target, key, userDef) {
    //是否是服務端渲染
    var shouldCache = !isServerRendering();
    //若是咱們把計算屬性的值寫成一個函數,這時函數默認爲計算屬性的get
    if(typeof userDef === 'function') {
        sharedPropertyDefinition.get = shouldCache ?
            //若是不是服務端渲染,則默認使用緩存,設置get爲createComputedGetter建立的緩存函數
            createComputedGetter(key) :
            //不然不使用緩存,直接設置get爲userDef這個咱們定義的函數
            userDef;
        //設置set爲空函數
        sharedPropertyDefinition.set = noop;
    } else {
        //若是咱們把計算屬性的值寫成一個對象,對象中可能包含set、get和cache三個字段
        sharedPropertyDefinition.get = userDef.get ?
            shouldCache && userDef.cache !== false ?
            //若是咱們傳入了get字段,且不是服務端渲染,且cache不爲false,設置get爲createComputedGetter建立的緩存函數
            createComputedGetter(key) : 
            //若是咱們傳入了get字段,可是是服務端渲染或者cache設爲了false,設置get爲userDef這個咱們定義的函數
            userDef.get :
            //若是沒有傳入get字段,設置get爲空函數
            noop;
        //設置set爲咱們傳入的傳入set字段或空函數
        sharedPropertyDefinition.set = userDef.set ?
            userDef.set :
            noop;
    }
    //雖然這裏能夠get、set均可以設置爲空函數
    //可是在項目中,get爲空函數對數據取值會報錯,set爲空函數對數據賦值會報錯
    //而computed主要做用就是計算取值的,因此get字段是必須的
    
    //數據劫持
    Object.defineProperty(target, key, sharedPropertyDefinition);
}

在上一篇的 vue響應式系統--observe、watcher、dep 中,我有關於Watcher的介紹中提到,計算屬性 watcher實例化的時候,會把options.lazy設置爲true,這裏是計算屬性惰性求值,且可緩存的關鍵,固然前提是cache不爲false。

cache不爲false,會調用createComputedGetter函數建立計算屬性的getter函數computedGetter,

先來看一段代碼

function createComputedGetter(key) {
    return function computedGetter() {
        var watcher = this._computedWatchers && this._computedWatchers[key];
        if(watcher) {
            if(watcher.dirty) {
                //watcher.evaluate中更新watcher的值,並把watcher.dirty設置爲false
                //這樣等下次依賴更新的時候纔會把watcher.dirty設置爲true,而後進行取值的時候纔會再次運行這個函數
                watcher.evaluate();
            }
            //依賴追蹤
            if(Dep.target) {
                watcher.depend();
            }
            //返回watcher的值
            return watcher.value
        }
    }
}

//對於計算屬性,當取值計算屬性時,發現計算屬性的watcher的dirty是true
//說明數據不是最新的了,須要從新計算,這裏就是從新計算計算屬性的值。
Watcher.prototype.evaluate = function evaluate() {
    this.value = this.get();
    this.dirty = false;
};

//當一個依賴改變的時候,通知它update
Watcher.prototype.update = function update() {
    //三種watcher,只有計算屬性 watcher的lazy設置了true,表示啓用惰性求值
    if(this.lazy) {
        this.dirty = true;
    } else if(this.sync) {
        //標記爲同步計算的直接運行run,三大類型暫無,因此基本會走下面的queueWatcher
        this.run();
    } else {
        //將watcher推入觀察者隊列中,下一個tick時調用。
        //也就是數據變化不是當即就去更新的,而是異步批量去更新的
        queueWatcher(this);
    }
};

當options.lazy設置爲true以後(僅計算屬性watcher的options.lazy設置爲true),每次依賴更新,都不會主動觸發run函數,而是把watcher.dirty設置爲true。這樣,當對計算屬性進行取值時,就會運行computedGetter函數,computedGetter函數中有一個關於watcher.dirty的判斷,當watcher.dirty爲true時會運行watcher.evaluate進行值的更新,並把watcher.dirty設置爲false,這樣就完成了惰性求值的過程。後面只要依賴不更新,就不會運行update,就不會把watcher.dirty爲true,那麼再次取值的時候就不會運行watcher.evaluate進行值的更新,從而達到了緩存的效果。

綜上,咱們瞭解到cache不爲false的時候,計算屬性都是惰性求值且具備緩存性的,而cache默認是true,咱們也大多使用這個默認值,因此咱們說 computed本質是一個惰性求值的觀察者,具備緩存性,只有當依賴變化後,第一次訪問 computed 屬性,纔會計算新的值

相關文章
相關標籤/搜索