最近深入研究了Vue實現響應性的部分源代碼,記錄了自己的壹些收獲和思考,希望對看這篇文章的人有所幫助。有問題請指出來,大家壹起進步。
什麽是響應式系統?
壹句話:數據變化驅動觀點更新。這樣我們就可以用“數據驅動”的思維來編寫我們的代碼,更加關註業務而不是dom操作。實際上,Vue響應的實現是壹個變化跟蹤和應用的過程。
Vue響應原理
通過數據劫持攔截數據變更;以依賴集合的方式觸發視圖更新。使用es5 Object.defineProperty攔截setter和getter的數據;Getter收集依賴項,setter觸發依賴項更新,而component render成為watcher回調並被添加到相應數據的依賴項中。
發布訂閱
采用發布-訂閱的設計模式,觀察者作為發布者,觀察者作為訂閱者。它們之間沒有直接的交互,通過Dep進行統壹調度。
觀察者負責攔截get、set;get時觸發dep添加依賴,設置時調度dep發布;添加Watcher將觸發訂閱數據的獲取,並將其添加到dep調度中心的訂戶隊列中。
下面的UML類圖是Vue實現響應式功能的類,以及它們之間的引用關系。
方法只包含部分屬性
上圖中的類已經標識的很清楚了,但是仍然需要壹個調用圖來使調用過程更加清晰,如下圖所示。
在響應數據對象中,被劫持的每個鍵的get/set函數關閉Dep調度實例。此圖顯示了密鑰更改期間的數據流。
部分源代碼
數據變更過程中的訂閱/發布模式在上圖中已經很清楚的展示出來了,從中我們已經知道,通過添加壹個watcher,可以訂閱某個數據變更。那麽,如果我們只需要以觀察者的身份訂閱組件渲染,那麽數據驅動視圖的渲染就水到渠成了。Vue就是這麽做的!
以下代碼片段來自Vue.prototype._mount函數。
callHook(虛擬機,“裝載前”)
vm。_watcher =新觀察器(vm,()= & gt{
vm。_更新(虛擬機。_render(),補水)
},noop)
補水=假
//手動裝入實例,調用裝入自身
//在其插入的掛鉤中為呈現創建的子組件調用mounted
如果(vm。$vnode == null) {
vm。_isMounted = true
調用掛鉤(虛擬機,“已安裝”)
}
思考壹些問題
#person分配壹個新對象。新對象中的屬性也有響應嗎?
var vm = new Vue({
埃爾:' #app ',
數據:()= & gt({
人:空
})
})
vm.person = {name: 'zs'}
setTimeout(()= & gt;{
//更改名稱
vm.person.name = 'finally zs '
}, 3000)
回答:有求必應。
原因:因為當Vue劫持設置時,它將再次觀察值。源代碼如下。
函數反應設置器(新值){
/* ...省略壹些代碼*/
//新值將在這裏再次被攔截。
childOb = observe(newVal)
dep.notify()
}
#當我們監聽多級屬性時,上級引用是否會發生變化,從而觸發回調?
var vm = new Vue({
數據:()= & gt({
人物:{姓名:'令狐洋蔥' }
}),
觀察:{
person.name'(val) {
console.log('名稱已更新',val)
}
}
})
vm.person = {}
回答:是的。
原因:當person.name作為表達式傳入Watcher時,會被解析成這樣的函數。
()= & gt{this.vm.person.name}
這將首先觸發person get,然後觸發nameget所以我們配置的回調函數,不僅加到了名字依賴上,也加到了person上。
#那麽最後壹個問題,如果給person分配了壹個新對象,如何對舊對象以及對舊對象的依賴進行垃圾回收?
舊對象的回收:由於舊對象的直接引用只是vue實例上的person,而person已經切換到新的引用,所以舊對象如果沒有被引用,就會被回收。
舊對象上的依賴項dep和觀察器的依賴項仍然存在;但run執行時會調用watcher的get()獲取當前值;新的依賴關系收集將在get中執行,舊的依賴關系將在收集後被清除。
具體源代碼如下:
/**
*評估getter,並重新收集依賴項。
*/
get () {
推送目標(this)
const value = this . getter . call(this.vm,this . VM)
//“觸摸”每個屬性,以便它們都被跟蹤為
//深度觀察的依賴性
如果(this.deep) {
遍歷(值)
}
popTarget()
this.cleanupDeps()
返回值
}
#當我們多次同步修改名稱時,回調函數會多次觸發嗎?
var vm = new Vue({
數據:()= & gt({
人物:{姓名:'令狐洋蔥' }
}),
觀察:{
person.name': (val) {
console.log('名稱已更新:'+ val)
}
}
})
vm.person = {name: 'zs'}
Vm.person.name = '無敵'
回答:不會,因為手表回調函數的執行是異步的,會重復。妳可以通過sync強制配置它同步運行,它會被執行兩次。
自己實現壹個響應式系統
只包含核心函數,具體源代碼可以看這裏。vuejs.org/v2/guide/reactivity.html.