vue-數據監聽與依賴收集

文章圍繞下面demo進行分析

<div id="app">
    <span>{{a.b}} {{c}} {{d}}</span>
</div>

<script>
    var app = new Vue({
        el: "#app",
        data: function(){
            return {
                a: {
                    b: 1
                },
                c: 1
            }
        },
        watch:{
          'a.b': function(){
            console.log(22)
          }
        },
        computed:{
          d: function (){
            return this.c
          }
        }
    });
</script>
數據監聽:從initData開始

vue監聽data數據的步驟可以概括爲下面幾步:

1、使用initData初始化data數據

2、將data掛載到vm._data中(後面會將_data賦值給$data,這裏的data如果是函數,就是返回的數據)

3、通過observe監聽數據

4、如果數據是一個非object類型的數據(typeof == object,且不爲null),終止程序

5、observe中使用new Observer生成ob

6、Observer中使用`this.dep = new Dep()`掛載dep

7、Observer中將__ob__指向this,所以可以使用`__ob__.dep`找到dep

8、遍歷屬性,使用defineReactive監聽屬性

9、defineReactive中生成`var dep = new Dep();`,這個dep是一個閉包

10、defineReactive中使用observe監聽屬性所代表的值,也就是步驟3,至此循環遞歸
依賴收集

三種watcher

1、normal-watcher(watch中的數據,通過initWatch生成) 記錄在_watchers中(所有的watch都會存放在這裏)

2、computed-watcher(computed) 記錄在_computedWatchers中

3、render-watcher 就是vm._watcher
normal-watcher:運行initWatch

我們在組件鉤子函數watch 中定義的,都屬於這種類型,即只要監聽的屬性改變了,都會觸發定義好的回調函數,這類watch的expression是計算屬性中的屬性名。

在初始化watch的時候(initWatch),會調用vm.watchvm.watch函數,vm.watch函數會直接使用Watcher構建觀察者對象。watch中屬性的值作爲watcher.cb存在,在觀察者update的時候,在watcher.run函數中執行。watch中屬性的key會進行parsePath處理,並用parsePath返回的函數,獲取watch的初始值value。

比如把a.b解析爲監聽a中的b屬性,這樣會先尋找a,也就是觸發a的get。

1、賦值cb爲a.b的值,賦值expression爲a.b

2、使用parsePath解析expOrFn,即a.b,並將返回的函數賦值給該watcher實例的getter即this.getter

3、運行this.get獲取a.b的值,進行依賴收集,this指向a.b的 watcher實例

4、運行pushTarget將Dep.target指向該watcher實例

5、運行this.getter,會先獲取a,運行defineReactive中的get

6、運行dep.depend(此時的dep指的是data.a的dep,在閉包中),進而運行Dep.target.addDep,將data.a的dep追加進該watcher實例中,並將該watcher實例追加進data.a的dep.subs中,因爲a具有__ob__,所以會運行a.__ob__.dep.depend,將a的dep追加進該watcher實例中,並將該watcher實例追加進a的dep.subs中

7、利用獲取到的a去獲取屬性b

8、運行dep.depend(此時的dep指的是a.b的dep,在閉包中),進而運行Dep.target.addDep,將a.b的dep追加進該watcher實例中,並將該watcher實例追加進a.b的dep.subs中,因爲b不具有__ob__,所以不會繼續追加

9、到這裏就獲取到了a.b的值1,並將這個值賦值給該watcher實例的value
computed-watcher:運行initComputed

我們在組件鉤子函數computed中定義的,都屬於這種類型,每一個 computed 屬性,最後都會生成一個對應的 watcher 對象,但是這類 watcher 有個特點:當計算屬性依賴於其他數據時,屬性並不會立即重新計算,只有之後其他地方需要讀取屬性的時候,它纔會真正計算,即具備 lazy(懶計算)特性。這類watch的expression是計算屬性中的屬性名。

在初始化computed的時候(initComputed),會先生成watch實例,然後監測數據是否已經存在data或props上,如果存在則拋出警告,否則調用defineComputed函數,監聽數據,爲組件中的屬性綁定getter及setter。

**注意:**computed中的屬性是直接綁定在vm上的,所以如果寫a.d,那就是屬性名是a.d,而不是a對象的屬性d。

1、執行initComputed,遍歷computed生成watch實例,並掛載到vm._computedWatchers上

    (1)賦值cb爲空函數,賦值expression爲expOrFn(d的值,函數或對象的get)的字符串形式,賦值this.getter爲expOrFn

    (2)默認的computed設置lazy爲true,不運行this.get獲取值,所以到這裏watch實例就生成了。

2、執行defineComputed函數,如果d的值是函數,或者d的cache屬性不是false,那麼會使用createComputedGetter函數生成computedGetter函數,作爲d的getter函數,如果cache設置爲false,不經過createComputedGetter封裝,每次獲取都會運行get,而d的setter就是他的set或者空函數(默認)

3、當獲取d的值時(比如渲染,此時Dep.target爲渲染watcher),會運行computedGetter函數

4、根據watcher.dirty的值決定是否運行watcher.evaluate重新獲取屬性值,這是懶計算的關鍵。dirty的值默認爲true,在依賴改變時或update時變爲true,在evaluate後變爲false

    (1)watcher.evaluate中運行this.get獲取d的值,進行依賴收集,this指向d的 watcher實例

    (2)運行pushTarget將Dep.target指向d的watcher實例

    (3)運行this.getter,會先獲取this.c的值,運行defineReactive中的get

    (4)運行dep.depend(此時的dep指的是data.c的dep,在閉包中),進而運行Dep.target.addDep,將data.c的dep追加進d的watcher實例中,並將d的watcher實例追加進data.c的dep.subs中

    (5)d的watcher出棧,將Dep.target重新設置爲渲染watcher

5、運行watcher.depend,遍歷watcher.deps(這裏主要是data.c的dep),將他們與渲染watcher互相關聯

**注意:**computed中的數據不經過Observer監聽,所以不存在dep

render-watcher:運行mountComponent掛載組件

每一個組件都會有一個 render-watcher, 當 data/computed 中的屬性改變的時候,會調用該 render-watcher 來更新組件的視圖。這類watch的expression是 function () {vm._update(vm._render(), hydrating);}

1、生成updateComponent函數,

2、實例化一個渲染watcher,把updateComponent當作expOrFn參數傳入

3、賦值cb爲空函數,賦值expression爲updateComponent的字符串形式,賦值this.getter爲expOrFn

4、運行this.get,進行依賴收集

5、運行pushTarget將Dep.target指向該渲染watcher實例

6、運行this.getter,即updateComponent函數

7、用render函數生成vnode,並將其作爲第一個參數,傳入_update

8、render函數中會對用到的變量進行getter操作,並完成依賴收集

    (1)獲取a,將data.a的dep追加進該渲染watcher實例中,並將該渲染watcher實例追加進data.a的dep.subs中

    (2)獲取a.b,將a.b的dep追加進該渲染watcher實例中,並將該渲染watcher實例追加進a.b的dep.subs中

    (3)獲取c,將data.c的dep追加進該渲染watcher實例中,並將該渲染watcher實例追加進data.c的dep.subs中

    (4)獲取d,運行d的getter函數computedGetter(詳情看上面computed-watcher中的步驟3-5)

9、完成依賴收集後,變量修改,會觸發dep.notify,通知渲染watcher實例的update操作,重新進行渲染
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章