天天看點

面試重點----vue源碼解析----資料綁定

首先需要明确兩個概念——

資料綁定:一旦更新data中的某個屬性資料,所有頁面上直接使用或者間接使用了該屬性的節點都會自動更新。

資料劫持:通過Object.defineProperty()來監視data中的所有屬性(任意層次)資料的變化,一旦資料發生變化就去更新界面。

它們的關系:資料綁定是我們的目的,而資料劫持是vue中用來實作資料綁定的方式。

劃重點:資料綁定的主角有兩位,一是data對象中的屬性:

data() {    //  注意這裡一共是四個屬性:name、friends、friends.name、friends.age

   return {

   name: 'bob'

   friends: {

    name: 'Mary'

    age: 28

   }

   }

}

二是頁面上使用了該屬性的節點,更具體的說,是模闆中的表達式,類似于:

如上所示,一個屬性可能會在模闆中被多個表達式使用;另一方面,一個表達式也可能會用到多個屬性(如上面的friends.name)——是以屬性和表達式之間是多對多的關系。

為了管理屬性和表達式之間錯綜複雜的關系,我們定義兩個重要的構造函數:Dep(dependency的簡寫)和Watcher。

Vue會為data中的每個屬性建立一個Dep的執行個體對象(記作dep),dep包含兩個屬性:唯一辨別符id,以及subs(subsribers的簡寫)。subs初始時為空數組,随後會往其中添加watcher,表示目前屬性在哪些表達式中被使用。

Vue還會為模闆中的每個表達式建立一個Watcher的執行個體對象(記作watcher),watcher的屬性有好幾個,其中包含一個對象屬性depIds,初始時為空對象,随後會往其中添加dep(鍵為dep.id,值為dep對象),表示目前表達式使用了哪些屬性。

一個屬性對應一個dep,一個表達式對應一個watcher,它們之間又互相引用,這就是響應式的基本架構,下面來看看具體是如何實作的。

vue檔案的解析主要有三步:資料代理、資料劫持、模闆解析。當資料代理完成後,Vue會周遊data中的屬性,每次将一個屬性的key、value,和data對象一起傳給defineReactive方法。

defineReactive方法最關鍵的部分就是新寫的get方法和set方法,其中get方法會在對屬性執行讀操作、且Dep.target有值時生效。這裡可以提前透露一下,Dep.target的值就是某個watcher,然而此時此刻watcher一個都還沒被建立,是以目前Dep.target的值為null,語句塊根本沒有機會被執行。而set方法要在資料修改時才生效,一時半會也沒法生效。是以等到後面觸發了這兩個方法我們再來對其進行解讀。

資料劫持完成後,下一步是模闆解析。在每條指令(如v-text="name")被解析完成後,會為其包含的表達式(如"name")建立一個watcher對象:

其中updateFn是模闆解析的核心操作:更新頁面,但在這裡不做讨論。我們隻需要知道建立的watcher對象有三個參數:vm對象、對應的表達式和一個回調函數。顯然。若對應的表達式中某個屬性被更改,便會調用該回調函數來實作響應式更新頁面。下面是Watcher執行個體化時的内部操作:

watcher執行個體有五個屬性,前三個就是把傳入的參數進行了儲存;第四個就是前面提到的depIds,在後面它會用來存儲相關的dep對象;而第五個屬性是——表達式的初始值,那就必須得擷取表達式中各個屬性的值,就會觸發這些屬性的get方法,而且此時Dep.target已經指向了目前新建立的watcher,于是讓我們再次傳回上面defineReactive方法中的get方法。

假設目前watcher對應的表達式是'friends.name',它将要依次和friends和friends.name兩個屬性建立聯系,但此時它的depIds還是空對象,friends和friends.name二者的subs也都是空數組。

讀屬性時,會觸發dep.depend()方法

dep.depend()方法中,執行了目前的watcher對象的addDep方法

為了避免重複添加,addDep方法中先執行了一個判斷,如果目前watcher和目前屬性之間還未建立聯系,便執行兩個最關鍵的操作:給dep添加新的sub(watcher) 、給watcher添加新的dep,禮尚往來。

這兩步完成後,目前的watcher.depIds中便加入了某個屬性對應的dep,而該屬性對應的dep.subs中也加入了目前的watcher。

類似的,接下來又會在目前的watcher和其它相關屬性之間建立聯系,結果就是watcher.depIds包含了多個屬性,而這些屬性的dep.subs分别加入目前的watcher。

目前watcher和所有相關屬性都建立了聯系後,下一個表達式又會建立一個watcher執行個體(記作watcher02),watcher02會和它的相關屬性都建立聯系。

這個感覺像是一個兩層循環,外層周遊watcher,内層周遊目前watcher對應的dep,最終實作多對多的連接配接。

至此,我們就完成了資料綁定中的初始化部分。還有一部分操作,發生在資料更改後,比如:執行vm.name = harden。

此時會觸發names屬性中的set方法:

核心操作是dep.notify()方法

這也很好了解,就是周遊目前屬性的subs,通知與之相關的watcher同步更新資料。

update隻是個過渡方法,起作用的是run方法,其功能可以用一句話表達:如果修改後的值與原來的值不等,則執行更新界面的回調函數。(還記得watcher執行個體化時傳入的那個回調函數嗎!)

至此,vue1.0的資料綁定就基本實作了,總結一下:

  在實作資料代理之後,模闆解析之前進行資料劫持——為data中每個屬性建立一個dep對象,并重寫這些屬性的get和set方法。在模闆解析過程中,為每個表達式建立一個watcher對象,由于建立過程中需要讀屬性值,是以會觸發屬性的get方法,在get方法内部watcher和dep之間建立起多對多的聯系;當data中某個屬性被修改,會觸發屬性的set方法,該屬性對應的dep會通知與之相關聯的所有watcher對象,同步更新資料,實作響應式更新。

vue