vue雙向資料繫結
vue.js是採用資料劫持
結合釋出者-訂閱者模式
的方式,通過Object.defineProperty()
來劫持各個屬性的setter
,getter
,在資料變動時釋出訊息給訂閱者,觸發相應的監聽回撥來渲染檢視。
具體步驟:
第一步:需要observer
的資料物件進行遞迴遍歷
,包括子屬性物件的屬性,都加上 setter和getter
這樣的話,給這個物件的某個值賦值
,就會觸發setter
,那麼就能監聽到了資料變化
第二步:compile
解析模板指令,將模板中的變數替換成資料,然後初始化渲染頁面檢視,並將每個指令對應的節點繫結更新函式,新增監聽資料的訂閱者,一旦資料有變動,收到通知,更新檢視
第三步:Watcher
Observer
和Compile
之間通訊的橋樑,主要做的事情是:
1、在自身例項化時往屬性訂閱器(dep
)裡面新增自己
2、自身必須有一個update
()方法
3、待屬性變動dep.notice()
通知時,能呼叫自身的update()方法
,並觸發Compile中繫結的回撥
,則功成身退。
第四步:MVVM
作為資料繫結的入口,整合Observer、Compile和Watcher三者,
通過Observer來監聽自己的model資料變化,通過Compile來解析編譯模板指令,最終利用Watcher搭起Observer和Compile之間的通訊橋樑,達到資料變化 -> 檢視更新;檢視互動變化(input) -> 資料model變更的雙向繫結效果。
進一步理解
1. 什麼是setter、getter
答:首先,別誤以為他們就是一會要說的get、set
物件有兩種屬性:
- 資料屬性:就是我們經常使用的屬性
- 訪問器屬性:也稱存取器屬性(存取器屬性就是一組獲取和設定值的函式)
資料屬性就是a和b;
get和set就是關鍵字 它們後面各自對應一個函式,這個函式就是上面紅字部分所講的,儲存器屬性。
get對應的方法稱為getter,負責獲取值,它不帶任何引數。set對應的方法為setter,負責設定值,在它的函式體中,一切的return都是無效的。
2. 什麼是Object.defineProperty() ?
答:我們先看一句定義:
物件是由多個名/值對組成的無序的集合。物件中每個屬性對應任意型別的值。
定義物件可以使用建構函式或字面量的形式:
除了以上新增屬性的方式,當然還可以使用Object.defineProperty定義新屬性或修改原有的屬性;
語法:
Object.defineProperty(obj, prop, descriptor)
引數:
obj:
必需。目標物件;prop:
必需。需定義或修改的屬性的名字;descriptor:
必需。目標屬性所擁有的特性;
返回值:
傳入函式的物件,即第一個引數obj;
OK,定義介紹完了,我們現在說一下一會關於雙向繫結我們要用到的知識點:存取器
描述;(誒?你是不是發現和上面好像有點關係)
對頭,它是這樣使用的:
Vue是採用資料劫持結合釋出/訂閱模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter,在資料變動時釋出訊息給訂閱者,觸發相應的監聽回撥。
在vue中v-model
,v-name
,{{}}
等都可以對資料進行展示,也就是說假如一個屬性都通過這三個指令了,那麼每當這個屬性改變的時候,相應的這個三個指令的html檢視也必須改變;
於是vue中就是每當有這樣的可能用到雙向繫結的指令,就在一個Dep
中增加一個訂閱者(addSub
),其訂閱者只是更新自己的指令對應的資料,也就是v-model='name'
和{{name}}
有兩個對應的訂閱者,各自管理自己的地方;
每當屬性的set方法觸發,就迴圈更新Dep
中的訂閱者(notify
);
就是Observer
一旦有了set
觸發,就會通知到Dep
,那Dep接到通知之後呢?從圖上來看,下面所講的就應該是Compile
了,也很簡單:
compile主要做的事情是解析模板指令,將模板中的變數替換成資料 主要做的工作
1)初始化,init的時候 初始化渲染頁面檢視;
2)將每個指令對應的節點繫結更新函式,新增監聽資料的訂閱者;
Dep
負責維護依賴,而訂閱者則來自於compile
,一旦有資料變動,則會繫結更新函式,此時也就是產生了訂閱者
,這個時候Dep
內就增加了一個訂閱者,
而一旦資料變動,則會收到通知,更新檢視;
好了,你是不是覺得上面這行說不通,或是讀不通,當然,因為上面的這個流程了缺少了,我們最後要說的Watcher
,我把上面這句話補全,就是Watcher的工作了;
Dep負責維護依賴,而訂閱者則來自於compile,一旦有資料變動,則會通過Watcher繫結更新函式,此時Watcher也向Dep中添加了訂閱者,一旦Dep接到Observer的通知,它就會再去通知Watcher,Watcher則會呼叫自身的update()方法,並觸發Compile中繫結的回撥,更新檢視;
最後敲黑板:
首先我們為每個vue屬性用Object.defineProperty()實現資料劫持,為每個屬性分配一個訂閱者集合的管理陣列dep;
然後在編譯的時候在該屬性的陣列dep中新增訂閱者,v-model會新增一個訂閱者,{{}}也會,v-bind也會,只要用到該屬性的指令理論上都會;
接著為input會新增監聽事件,修改值就等於為該屬性賦值,則會觸發該屬性的set方法,在set方法內通知訂閱者陣列dep,訂閱者陣列迴圈呼叫各訂閱者的update方法更新檢視。
這樣我們就能實現js的雙向資料繫結,隨著文字框輸入文字的變化,span中會同步顯示相同的文字內容;這樣就實現了model => view
以及view => model
的雙向繫結。
通過給in
輸入框新增事件監聽input
來觸發obj物件的set方法
,而set再修改了訪問器屬性的同時,也修改了dom樣式,改變了span標籤內的文字。