您好,登錄后才能下訂單哦!
Vue 采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式來實(shí)現(xiàn)數(shù)據(jù)的響應(yīng)式,通過Object.defineProperty來劫持?jǐn)?shù)據(jù)的setter,getter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,訂閱者收到消息后進(jìn)行相應(yīng)的處理。
要實(shí)現(xiàn)mvvm的雙向綁定,就必須要實(shí)現(xiàn)以下幾點(diǎn):
Compile—指令解析系統(tǒng),對每個元素節(jié)點(diǎn)的指令進(jìn)行掃描和解析,根據(jù)指令模板替換數(shù)據(jù),以及綁定相應(yīng)的更新函數(shù)
Observer—數(shù)據(jù)監(jiān)聽系統(tǒng),能夠?qū)?shù)據(jù)對象的所有屬性進(jìn)行監(jiān)聽,如有變動可拿到最新值并通知訂閱者
Dep+Watcher—發(fā)布訂閱模型,作為連接Observer和Compile的橋梁,能夠訂閱并收到每個屬性變動的通知,執(zhí)行指令綁定的相應(yīng)回調(diào)函數(shù),從而更新視圖。
Dep是發(fā)布訂閱者模型中的發(fā)布者:get數(shù)據(jù)的時候,收集訂閱者,觸發(fā)Watcher的依賴收集;set數(shù)據(jù)時發(fā)布更新,通知Watcher 。一個Dep實(shí)例對應(yīng)一個對象屬性或一個被觀察的對象,用來收集訂閱者和在數(shù)據(jù)改變時,發(fā)布更新。
Watcher是發(fā)布訂閱者模型中的訂閱者:訂閱的數(shù)據(jù)改變時執(zhí)行相應(yīng)的回調(diào)函數(shù)(更新視圖或表達(dá)式的值)。一個Watcher可以更新視圖,如html模板中用到的{{test}},也可以執(zhí)行一個$watch監(jiān)督的表達(dá)式的回調(diào)函數(shù)(Vue實(shí)例中的watch項(xiàng)底層是調(diào)用的$watch實(shí)現(xiàn)的),還可以更新一個計(jì)算屬性(即Vue實(shí)例中的computed項(xiàng))。
mvvm入口函數(shù),整合以上三者,具體如圖所示:
compire可以參看《雙向綁定的實(shí)現(xiàn)原理》,這里不做過多解讀。
Observer,Dep和Watcher類的實(shí)現(xiàn)及原理,推薦閱讀《Vue源碼解讀一:Vue數(shù)據(jù)響應(yīng)式原理》,一般開發(fā)者需要關(guān)注:
Watcher,作用是分割表達(dá)式,收集依賴并且在值變化的時候調(diào)用回調(diào)函數(shù)。
我們上面說過一個Dep對應(yīng)著一個數(shù)據(jù)(這個數(shù)據(jù)可能是:對象的屬性、一個對象、一個數(shù)組);一個Watcher對應(yīng)可以是一個模板也可以是一個$watch對應(yīng)的表達(dá)式、函數(shù)等,無論那種情況,他們都依賴于data里面的數(shù)據(jù),所以這里說的依賴其實(shí)就是模板或表達(dá)式所依賴的數(shù)據(jù),對應(yīng)著相關(guān)數(shù)據(jù)的Dep。
第一種:觀察模板中的數(shù)據(jù)
第二種:觀察創(chuàng)建Vue實(shí)例時watch選項(xiàng)里的數(shù)據(jù)
第三種:觀察創(chuàng)建Vue實(shí)例時computed選項(xiàng)里的數(shù)據(jù)所依賴的數(shù)據(jù)
第四種:調(diào)用$watch api觀察的數(shù)據(jù)或表達(dá)式
Watcher只有在這四種場景中,Watcher才會收集依賴,更新模板或表達(dá)式,否則,數(shù)據(jù)改變后,無法通知依賴這個數(shù)據(jù)的模板或表達(dá)式:
所以在解決數(shù)據(jù)改變,模板或表達(dá)式?jīng)]有改變的問題時,可以這么做:
首先仔細(xì)看一看數(shù)據(jù)是否在上述四種應(yīng)用場景中,以便確認(rèn)數(shù)據(jù)已經(jīng)收集依賴;其次查看改變數(shù)據(jù)的方式,確定這種方式會使數(shù)據(jù)的改變被攔截(關(guān)于這一點(diǎn),上面Obsever相關(guān)內(nèi)容中說的比較多)。
當(dāng)對象增刪的時候,是監(jiān)控不到的。比如:data={a:"a"},這個時候如果我們設(shè)置data.test="test",這個時候是監(jiān)控不到的。因?yàn)樵趏bserve data的時候,會遍歷已有的每個屬性(比如a),添加getter/setter,而后面設(shè)置的test屬性并沒有機(jī)會設(shè)置getter/setter,所以檢測不到變化。同樣的,刪除對象屬性的時候,getter/setter會跟著屬性一起被刪除掉,攔截不到變化。
vm.$set/Vue.set和vm.$delete/Vue.delete這樣的api來解決這個問題
getter/setter是針對對象的對于數(shù)組的修改(push(),pop(),shift(),unshift(),splice(),sort(),reverse())等方法,arr發(fā)生了改變,此時是需要更新視圖的,但是arr的getter/setter攔截不到變化(只有在賦值的時候才會調(diào)用setter,比如:arr=[6,7,8])。
對于這種情況,vue通過改寫Array的默認(rèn)方法,在調(diào)用這些方法的時候發(fā)布更新消息。一般無需關(guān)注,但是對于如下兩種情況:
當(dāng)你利用索引直接設(shè)置一個項(xiàng)時,例如:vm.items[indexOfItem] = newValue
當(dāng)你修改數(shù)組的長度時,例如:vm.items.length = newLength
需要vm.$set/Vue.set和vm.items.splice(newLength)解決,具體參看官方說明
每次給數(shù)據(jù)設(shè)置值得時候,都會調(diào)用setter函數(shù),這個時候就會發(fā)布屬性更新消息,即使數(shù)據(jù)的值沒有變。從性能方便考慮我們肯定希望值沒有變化的時候,不更新模板。(像Angular這樣把批量操作延時到一次更新,一次做完所有數(shù)據(jù)變更,然后整體應(yīng)用到界面上)
整體感知virtual DOM
virtual DOM分為三個步驟:
1.createElement(): 用 JavaScript對象(虛擬樹) 描述 真實(shí)DOM對象(真實(shí)樹)
2.diff(oldNode, newNode) : 對比新舊兩個虛擬樹的區(qū)別,收集差異
3.patch() : 將差異應(yīng)用到真實(shí)DOM樹
有的時候 第二步 可能與 第三步 合并成一步(Vue 中的patch就是這樣)
Vue的實(shí)現(xiàn)原理總結(jié)
首先,在實(shí)例化的過程中,把一個普通 JavaScript 對象傳給 Vue 實(shí)例的 data選項(xiàng),Vue 將遍歷此對象所有的屬性,并使用 Object.defineProperty 把這些屬性全部轉(zhuǎn)為 getter/setter。
Dep 是一個依賴收集器。data 下的每一個屬性都有一個唯一的 Dep 對象,在 get 中收集僅針對該屬性的依賴,然后在 set 方法中觸發(fā)所有收集的依賴。
在Watcher中對表達(dá)式求值,從而觸發(fā)數(shù)據(jù)的get。在求值之前將當(dāng)前Watch實(shí)例設(shè)置到全局,使用pushTarget(this)方法。
在get()中收集依賴,this.subs.push(sub),set的時候觸發(fā)回調(diào)Dep.notify()。
Compile中首先將template或el編譯成render函數(shù),render函數(shù)返回一個虛擬DOM對象(將模板轉(zhuǎn)為 render 函數(shù)的時候,實(shí)際是先生成的抽象語法樹(AST),再將抽象語法樹轉(zhuǎn)成的 render 函數(shù))
當(dāng) vm._render 執(zhí)行的時候,所依賴的變量就會被求值,并被收集為依賴。按照Vue中 watcher.js 的邏輯,當(dāng)依賴的變量有變化時不僅僅回調(diào)函數(shù)被執(zhí)行,實(shí)際上還要重新求值,即還要執(zhí)行一遍
如果還沒有 prevVnode 說明是首次渲染,直接創(chuàng)建真實(shí)DOM。如果已經(jīng)有了 prevVnode 說明不是首次渲染,那么就采用 patch 算法進(jìn)行必要的DOM操作。這就是Vue更新DOM的邏輯。
最后,安利下:《Vue.js 技術(shù)揭秘》
參考文章
梳理Vue2.0雙向綁定的實(shí)現(xiàn)原理
文自《梳理vue雙向綁定的實(shí)現(xiàn)原理 - vue入坑總結(jié) - 周陸軍的個人網(wǎng)站》,如有不妥之前,請?jiān)凑玖粞愿嬷?/p>
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。