文章圍繞下面demo進行分析vue
<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>
vue監聽data數據的步驟能夠歸納爲下面幾步:node
一、使用initData初始化data數據 二、將data掛載到vm._data中(後面會將_data賦值給$data,這裏的data若是是函數,就是返回的數據) 三、經過observe監聽數據 四、若是數據是一個非object類型的數據(typeof == object,且不爲null),終止程序 五、observe中使用new Observer生成ob 六、Observer中使用`this.dep = new Dep()`掛載dep 七、Observer中將__ob__指向this,因此可使用`__ob__.dep`找到dep 八、遍歷屬性,使用defineReactive監聽屬性 九、defineReactive中生成`var dep = new Dep();`,這個dep是一個閉包 十、defineReactive中使用observe監聽屬性所表明的值,也就是步驟3,至此循環遞歸
三種watcherexpress
一、normal-watcher(watch中的數據,經過initWatch生成) 記錄在_watchers中(全部的watch都會存放在這裏) 二、computed-watcher(computed) 記錄在_computedWatchers中 三、render-watcher 就是vm._watcher
咱們在組件鉤子函數watch 中定義的,都屬於這種類型,即只要監聽的屬性改變了,都會觸發定義好的回調函數,這類watch的expression是計算屬性中的屬性名。閉包
在初始化watch的時候(initWatch),會調用vm.$watch函數,vm.$watch函數會直接使用Watcher構建觀察者對象。watch中屬性的值做爲watcher.cb存在,在觀察者update的時候,在watcher.run函數中執行。watch中屬性的key會進行parsePath處理,並用parsePath返回的函數,獲取watch的初始值value。app
好比把a.b解析爲監聽a中的b屬性,這樣會先尋找a,也就是觸發a的get。函數
一、賦值cb爲a.b的值,賦值expression爲a.b 二、使用parsePath解析expOrFn,即a.b,並將返回的函數賦值給該watcher實例的getter即this.getter 三、運行this.get獲取a.b的值,進行依賴收集,this指向a.b的 watcher實例 四、運行pushTarget將Dep.target指向該watcher實例 五、運行this.getter,會先獲取a,運行defineReactive中的get 六、運行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中 七、利用獲取到的a去獲取屬性b 八、運行dep.depend(此時的dep指的是a.b的dep,在閉包中),進而運行Dep.target.addDep,將a.b的dep追加進該watcher實例中,並將該watcher實例追加進a.b的dep.subs中,由於b不具備__ob__,因此不會繼續追加 九、到這裏就獲取到了a.b的值1,並將這個值賦值給該watcher實例的value
咱們在組件鉤子函數computed中定義的,都屬於這種類型,每個 computed 屬性,最後都會生成一個對應的 watcher 對象,可是這類 watcher 有個特色:當計算屬性依賴於其餘數據時,屬性並不會當即從新計算,只有以後其餘地方須要讀取屬性的時候,它纔會真正計算,即具有 lazy(懶計算)特性。這類watch的expression是計算屬性中的屬性名。this
在初始化computed的時候(initComputed),會先生成watch實例,而後監測數據是否已經存在data或props上,若是存在則拋出警告,不然調用defineComputed函數,監聽數據,爲組件中的屬性綁定getter及setter。lua
注意:computed中的屬性是直接綁定在vm上的,因此若是寫a.d,那就是屬性名是a.d,而不是a對象的屬性d。spa
一、執行initComputed,遍歷computed生成watch實例,並掛載到vm._computedWatchers上 (1)賦值cb爲空函數,賦值expression爲expOrFn(d的值,函數或對象的get)的字符串形式,賦值this.getter爲expOrFn (2)默認的computed設置lazy爲true,不運行this.get獲取值,因此到這裏watch實例就生成了。 二、執行defineComputed函數,若是d的值是函數,或者d的cache屬性不是false,那麼會使用createComputedGetter函數生成computedGetter函數,做爲d的getter函數,若是cache設置爲false,不通過createComputedGetter封裝,每次獲取都會運行get,而d的setter就是他的set或者空函數(默認) 三、當獲取d的值時(好比渲染,此時Dep.target爲渲染watcher),會運行computedGetter函數 四、根據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 五、運行watcher.depend,遍歷watcher.deps(這裏主要是data.c的dep),將他們與渲染watcher互相關聯
注意:computed中的數據不通過Observer監聽,因此不存在depcode
每個組件都會有一個 render-watcher, 當 data/computed 中的屬性改變的時候,會調用該 render-watcher 來更新組件的視圖。這類watch的expression是 function () {vm._update(vm._render(), hydrating);}
。
一、生成updateComponent函數, 二、實例化一個渲染watcher,把updateComponent看成expOrFn參數傳入 三、賦值cb爲空函數,賦值expression爲updateComponent的字符串形式,賦值this.getter爲expOrFn 四、運行this.get,進行依賴收集 五、運行pushTarget將Dep.target指向該渲染watcher實例 六、運行this.getter,即updateComponent函數 七、用render函數生成vnode,並將其做爲第一個參數,傳入_update 八、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) 九、完成依賴收集後,變量修改,會觸發dep.notify,通知渲染watcher實例的update操做,從新進行渲染