vuejs如何實現(xiàn)數(shù)據(jù)雙向綁定
實現(xiàn)數(shù)據(jù)綁定的做法有大致如下幾種:
發(fā)布者-訂閱者模式(backbone.js)
臟值檢查(angular.js)
數(shù)據(jù)劫持(vue.js)
發(fā)布者-訂閱者模式:
一般通過sub, pub的方式實現(xiàn)數(shù)據(jù)和視圖的綁定監(jiān)聽虐沥,更新數(shù)據(jù)方式通常做法是vm.set('property', value)
启昧,這里有篇文章講的比較詳細袍啡,有興趣可點這里
這種方式現(xiàn)在畢竟太low了,我們更希望通過vm.property = value
這種方式更新數(shù)據(jù)嗜傅,同時自動更新視圖,于是有了下面兩種方式
臟值檢查:
angular.js 是通過臟值檢測的方式比對數(shù)據(jù)是否有變更,來決定是否更新視圖医清,最簡單的方式就是通過setInterval()
定時輪詢檢測數(shù)據(jù)變動量蕊,當然Google不會這么low铺罢,angular只有在指定的事件觸發(fā)時進入臟值檢測,大致如下:
DOM事件残炮,譬如用戶輸入文本韭赘,點擊按鈕等。( ng-click )
XHR響應事件 ( $http )
瀏覽器Location變更事件 ( $location )
Timer事件( interval )
執(zhí)行 apply()
數(shù)據(jù)劫持:
vue.js 則是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式势就,通過Object.defineProperty()
來劫持各個屬性的setter
泉瞻,getter
,在數(shù)據(jù)變動時發(fā)布消息給訂閱者苞冯,觸發(fā)相應的監(jiān)聽回調(diào)袖牙。
主要的知識點:
1.Vue雙向綁定原理(一)文檔片段DocumentFragment
2.Vue雙向綁定原理(二)訪問器屬性defineProperty()和發(fā)布/訂閱模式
1、文檔片段DocumentFragment
當我們更新少量dom節(jié)點的時候舅锄,可以創(chuàng)建他們?nèi)缓笾苯觓ppendChild()插入DOM樹鞭达。但是如果我們要創(chuàng)建大量節(jié)點的時候,每次都創(chuàng)建再插入巧娱,會調(diào)用很多次appendChild()方法碉怔,會非常浪費性能。為了解決這個問題禁添,就有了documentFragmeng文檔片段撮胧,可以先把這些創(chuàng)建的元素放入文檔片段,然后在把文檔片段插入DOM樹老翘,這樣就只會調(diào)用一次appendChild()方法了芹啥。
createDocumentFragment()
用于創(chuàng)建一個文檔片段作為容器,其中可以包含多個dom節(jié)點铺峭。這里有兩點需要特別注意的地方:
當把文檔片段插入DOM樹的時候墓怀,只會把它的子節(jié)點插進去,它作為容器本身是不會進入DOM樹的卫键。
當把DOM樹種的節(jié)點插入文檔片段的時候傀履,這些節(jié)點,會真的從DOM樹種消失莉炉。我們也把這個過程叫做劫持钓账。
在Vue中的作用
上邊說清楚了documentFragment是干嘛的碴犬,現(xiàn)在說說他在vue中的作用。
每個vue實例都有一個根元素id的屬性el梆暮,Vue對象通過它來找到要渲染的部分服协。之后使用createDocumentFragment()方法創(chuàng)建一個documentFragment,遍歷根元素的所有子元素啦粹,依次劫持并插入文檔片段偿荷,將根元素掏空。然后執(zhí)行Vue的編譯:遍歷documentFragment中的節(jié)點唠椭,對其中的v-for,v-text等屬性進行相應的處理跳纳。最后,把編譯完成后的documentFragment還給根元素泪蔫。
這也就是為什么棒旗,我們寫在模板中的HTML,有v-for,v-model等屬性撩荣,而實際頁面F12之后卻沒有铣揉,因為那是Vue編譯之后返回的結(jié)果。
2餐曹、訪問器屬性
js的對象有兩種屬性:數(shù)據(jù)屬性和訪問器屬性逛拱。
1.數(shù)據(jù)屬性
數(shù)據(jù)屬性包含一個數(shù)據(jù)值的位置。這個位置可以讀取和寫入值台猴。數(shù)據(jù)屬性也就是我們最常見的對象屬性朽合。數(shù)據(jù)屬性有4個描述他行為的特性:
Configurable: 能否用delete刪除屬性從而重新定義屬性。默認為false
Enumerable: 能否通過for-in遍歷饱狂,即是否可枚舉曹步。默認為false
Writable: 是否能修改屬性的值。默認為false
Value: 包含這個屬性的數(shù)據(jù)值休讳,讀寫屬性的時候其實就在這里讀寫讲婚。默認為undefined
要修改屬性的上述4個默認特性,就必須使用ECMAScript的Object.defineProperty()方法俊柔,該方法包含3個參數(shù):屬性所在的對象筹麸,屬性名,描述符對象雏婶。描述符對象的屬性必須在上述4個屬性中物赶。例如:
var person = {};
Object.defineProperty(person,"name",{
writable: false,
value: "Nicholas"
});
alert(person.name); // "Nicholas"
person.name = "Tom";
alert(person.name); // "Nicholas"
上例創(chuàng)建了一個不可寫的name屬性并賦值。所以無法修改留晚。
注意酵紫,一旦把Configurable屬性設置為false,就無法再將其變回true了奖地,此時再想修改特性鹉动,就都會報錯了。
2.訪問器屬性
訪問器屬性不包含數(shù)據(jù)值宏邮,他們包含一對getter和setter函數(shù)(非必須)泽示。在讀寫訪問器屬性的值的時候蜜氨,會調(diào)用相應的getter和setter函數(shù),而我們的vue就是在getter和setter函數(shù)中增加了我們需要的操作飒炎。
訪問器屬性有以下4個特性:
Configurable: 能否用delete刪除屬性從而重新定義屬性埋哟。默認false
Enumerable: 能否通過for-in遍歷,即是否可枚舉郎汪。默認false
get: 讀取屬性時調(diào)用的函數(shù),默認undefined
set: 寫入屬性時調(diào)用的函數(shù)抛计,默認undefined
在Vue中的作用
Vue會遍歷實例的data屬性照筑,把每一個data都設置為訪問器,然后在該屬性的getter函數(shù)中將其設為watcher波俄,在setter中向其他watcher發(fā)布改變的消息蛾默。這樣,配合發(fā)布/訂閱模式阀趴,改變其中的一個值苍匆,會發(fā)布消息,所有的watcher會更新自己叔汁,這些watcher也就是綁定在dom中的顯示信息,比如 v-text=”year” 和 {{ year }} 這些節(jié)點据块。從而達到改變?yōu)gdom另假,在瀏覽器中實時變化的效果
(Object.defineProperty 是僅 ES5 支持,且無法 shim 的特性边篮,這也就是為什么 Vue 不支持 IE8 以及更低版本瀏覽器的原因。)
數(shù)據(jù)劫持:
vue.js 則是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式凌受,通過Object.defineProperty()來劫持各個屬性的setter思杯,getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者誊册,觸發(fā)相應的監(jiān)聽回調(diào)杈湾。
大致思路: 首先Vue會使用documentfragment劫持根元素里包含的所有節(jié)點,這些節(jié)點不僅包括標簽元素殴泰,還包括文本浮驳,甚至換行的回車。
然后Vue會把data中所有的數(shù)據(jù)离咐,用defindProperty()變成Vue的訪問器屬性奉件,這樣每次修改這些數(shù)據(jù)的時候,就會觸發(fā)相應屬性的get术陶,set方法煤痕。
接下來編譯處理劫持到的dom節(jié)點接谨,遍歷所有節(jié)點脓豪,根據(jù)nodeType來判斷節(jié)點類型忌卤,根據(jù)節(jié)點本身的屬性(是否有v-model等屬性)或者文本節(jié)點的內(nèi)容(是否符合{{文本插值}}的格式)來判斷節(jié)點是否需要編譯。對v-model历谍,綁定事件當輸入的時候辣垒,改變Vue中的數(shù)據(jù)印蔬。對文本節(jié)點侥猬,將他作為一個觀察者watcher放入觀察者列表,當Vue數(shù)據(jù)改變的時候鹃锈,會有一個主題對象瞧预,對列表中的觀察者們發(fā)布改變的消息,觀察者們再更新自己盆驹,改變節(jié)點中的顯示滩愁,從而達到雙向綁定的目的。
思路整理:
已經(jīng)了解到vue是通過數(shù)據(jù)劫持的方式來做數(shù)據(jù)綁定的廉丽,其中最核心的方法便是通過Object.defineProperty()
來實現(xiàn)對屬性的劫持妻味,達到監(jiān)聽數(shù)據(jù)變動的目的弧可,無疑這個方法是本文中最重要劣欢、最基礎的內(nèi)容之一裁良,如果不熟悉defineProperty,猛戳這里整理了一下牧抵,要實現(xiàn)mvvm的雙向綁定侨把,就必須要實現(xiàn)以下幾點:
1秋柄、實現(xiàn)一個數(shù)據(jù)監(jiān)聽器Observer,能夠?qū)?shù)據(jù)對象的所有屬性進行監(jiān)聽省店,如有變動可拿到最新值并通知訂閱者
2笨触、實現(xiàn)一個指令解析器Compile,對每個元素節(jié)點的指令進行掃描和解析粗俱,根據(jù)指令模板替換數(shù)據(jù)虚吟,以及綁定相應的更新函數(shù)
解析器Compile實現(xiàn)步驟:
(1).解析模板指令,并替換模板數(shù)據(jù)废麻,初始化視圖
(2).將模板指令對應的節(jié)點綁定對應的更新函數(shù)模庐,初始化相應的訂閱器
為了解析模板掂碱,首先需要獲取到dom元素,然后對含有dom元素上含有指令的節(jié)點進行處理沧卢,因此這個環(huán)節(jié)需要對dom操作比較頻繁醉者,所有可以先建一個fragment片段披诗,將需要解析的dom節(jié)點存入fragment片段里再進行處理
3呈队、實現(xiàn)一個Watcher唱歧,作為連接Observer和Compile的橋梁,能夠訂閱并收到每個屬性變動的通知几于,執(zhí)行指令綁定的相應回調(diào)函數(shù)沿后,從而更新視圖
4、mvvm入口函數(shù)膝蜈,整合以上三者