簡單實現(xiàn)Vue的observer和watcher
非庖丁瞎解牛系列~ =。=
在日常項目開發(fā)的時候,我們將js對象傳給vue實例中的data選項,來作為其更新視圖的基礎,事實上是vue將會遍歷它的屬性,用Object.defineProperty 設置它們的 get/set,從而讓 data 的屬性能夠響應數(shù)據(jù)變化:
Object.defineProperty(obj, name, {
// 獲取值的時候先置入vm的_data屬性對象中
get() {
// 賦值的時候顯示的特性
},
set() {
// 值變化的時候可以做點什么
}
})
接下來可以利用其實現(xiàn)一個最簡單的watcher.既然要綁定數(shù)據(jù)執(zhí)行回調(diào)函數(shù),data屬性和callback屬性是少不了的,我們定義一個vm對象(vue中vm對象作為根實例,是全局的):
/**
* @param {Object} _data 用于存放data值
* @param {Object} $data data原始數(shù)據(jù)對象,當前值
* @param {Object} callback 回調(diào)函數(shù)
*/
var vm = { _data: {}, $data: {}, callback: {} }
在設置值的時候,如果檢測到當前值與存儲在_data中的對應值發(fā)生變化,則將值更新,并執(zhí)行回調(diào)函數(shù),利用Object.definedProperty方法中的get() & set() 我們很快就可以實現(xiàn)這個功能~
vm.$watch = (obj, func) => {
// 回調(diào)函數(shù)
vm.callback[ obj ] = func
// 設置data
Object.defineProperty(vm.$data, obj, {
// 獲取值的時候先置入vm的_data屬性對象中
get() {
return vm._data[ obj ]
},
set(val) {
// 比較原值,不相等則賦值,執(zhí)行回調(diào)
if (val !== vm._data[ obj ]) {
vm._data[ obj ] = val
const cb = vm.callback[ obj ]
cb.call(vm)
}
}
})
}
vm.$watch('va', () => {console.log('已經(jīng)成功被監(jiān)聽啦')})
vm.$data.va = 1
雖然初步實現(xiàn)了這個小功能,那么問題來了,obj對象如果只是一個簡單的值為值類型的變量,那以上代碼完全可以滿足;但是如果obj是一個具有一層甚至多層樹結構對象變量,我們就只能監(jiān)聽到最外層也就是obj本身的變化,內(nèi)部屬性變化無法被監(jiān)聽(沒有設置給對應屬性設置set和get),因為對象自身內(nèi)部屬性層數(shù)未知,理論上可以無限層(一般不會這么做),所以此處還是用遞歸解決吧~
咱們先將Object.defineProperty函數(shù)剝離,一是解耦,二是方便我們遞歸~
var defineReactive = (obj, key) => {
Object.defineProperty(obj, key, {
get() {
return vm._data[key]
},
set(newVal) {
if (vm._data[key] === newVal) {
return
}
vm._data[key] = newVal
const cb = vm.callback[ obj ]
cb.call(vm)
}
})
}
咦,說好的遞歸呢,不著急,上面只是抽離了加get和set功能的函數(shù),
現(xiàn)在我們加入遞歸~
var Observer = (obj) => {
// 遍歷,讓對象中的每個屬性可以加上get set
Object.keys(obj).forEach((key) =>{
defineReactive(obj, key)
})
}
這里僅僅只是遍歷,要達到遞歸,則需要在defineReactive的時候再加上判斷,判斷這個屬性是否為object類型,如果是,則執(zhí)行Observer自身~我們改寫下defineReactive函數(shù)
// 判斷是否為object類型,是就繼續(xù)執(zhí)行自身
var observe = (value) => {
// 判斷是否為object類型,是就繼續(xù)執(zhí)行Observer
if (!value || typeof value !== 'object') {
return
}
return new Observer(value)
}
// 將observe方法置入defineReactive中Object.defineProperty的set中,形成遞歸
var defineReactive = (obj, key) => {
// 判斷val是否為對象,如果對象有很多層屬性,則這邊的代碼會不斷調(diào)用自身(因為observe又執(zhí)行了Observer,而Observer執(zhí)行defineReactive),一直到最后一層,從最后一層開始執(zhí)行下列代碼,層層返回(可以理解為洋蔥模型),直到最前面一層,給所有屬性加上get/set
var childObj = observe(vm._data[key])
Object.defineProperty(obj, key, {
get() {
return vm._data[key]
},
set(newVal) {
// 如果設置的值完全相等則什么也不做
if (vm._data[key] === newVal) {
return
}
// 不相等則賦值
vm._data[key] = newVal
// 執(zhí)行回調(diào)
const cb = vm.callback[ key ]
cb.call(vm)
// 如果set進來的值為復雜類型,再遞歸它,加上set/get
childObj = observe(val)
}
})
}
現(xiàn)在我們來整理下,把我們剛開始實現(xiàn)的功能雛形進行進化
var vm = { _data: {}, $data: {}, callback: {}}
var defineReactive = (obj, key) => {
// 一開始的時候是不設值的,所以,要在外面做一套observe
// 判斷val是否為對象,如果對象有很多層屬性,則這邊的代碼會不斷調(diào)用自身(因為observe又執(zhí)行了Observer,而Observer執(zhí)行defineReactive),一直到最后一層,從最后一層開始執(zhí)行下列代碼,層層返回(可以理解為洋蔥模型),直到最前面一層,給所有屬性加上get/set
var childObj = observe(vm._data[key])
Object.defineProperty(obj, key, {
get() {
return vm._data[key]
},
set(newVal) {
if (vm._data[key] === newVal) {
return
}
// 如果值有變化的話,做一些操作
vm._data[key] = newVal
// 執(zhí)行回調(diào)
const cb = vm.callback[ key ]
cb.call(vm)
// 如果set進來的值為復雜類型,再遞歸它,加上set/get
childObj = observe(newVal)
}
})
}
var Observer = (obj) => {
Object.keys(obj).forEach((key) =>{
defineReactive(obj, key)
})
}
var observe = (value) => {
// 判斷是否為object類型,是就繼續(xù)執(zhí)行Observer
if (!value || typeof value !== 'object') {
return
}
Observer(value)
}
vm.$watch = (name, func) => {
// 回調(diào)函數(shù)
vm.callback[name] = func
// 設置data
defineReactive(vm.$data, name)
}
// 綁定a,a若變化則執(zhí)行回調(diào)方法
var va = {a:{c: 'c'}, b:{c: 'c'}}
vm._data[va] = {a:{c: 'c'}, b:{c: 'c'}}
vm.$watch('va', () => {console.log('已經(jīng)成功被監(jiān)聽啦')})
vm.$data.va = 1
在谷歌瀏覽器的console中粘貼以上代碼,然后回車發(fā)現(xiàn),結果不出所料,va本身被監(jiān)聽了,可以,我們試試va的內(nèi)部屬性有沒有被監(jiān)聽,改下vm.data.va=1為vm.data.va.a = 1,結果發(fā)現(xiàn)報錯了
什么鬼?
我們又仔細檢查了代碼,WTF,原來我們在遞歸的時候,Object.defineProperty中的回調(diào)函數(shù)cb的key參數(shù)一直在發(fā)生變化,我們希望的是里面的屬性變化的時候執(zhí)行的是我們事先定義好的回調(diào)函數(shù)~那么我們來改下方法,將一開始定義好的回調(diào)作為參數(shù)傳進去,確保每一層遞歸set的回調(diào)都是我們事先設置好的~
var vm = { _data: {}, $data: {}, callback: {}}
var defineReactive = (obj, key, cb) => {
// 一開始的時候是不設值的,所以,要在外面做一套observe
var childObj = observe(vm._data[key], cb)
Object.defineProperty(obj, key, {
get() {
return vm._data[key]
},
set(newVal) {
if (vm._data[key] === newVal) {
return
}
// 如果值有變化的話,做一些操作
vm._data[key] = newVal
// 執(zhí)行回調(diào)
cb()
// 如果set進來的值為復雜類型,再遞歸它,加上set/get
childObj = observe(newVal)
}
})
}
var Observer = (obj, cb) => {
Object.keys(obj).forEach((key) =>{
defineReactive(obj, key, cb)
})
}
var observe = (value, cb) => {
// 判斷是否為object類型,是就繼續(xù)執(zhí)行Observer
if (!value || typeof value !== 'object') {
return
}
Observer(value, cb)
}
vm.$watch = (name, func) => {
// 回調(diào)函數(shù)
vm.callback[name] = func
// 設置data
defineReactive(vm.$data, name, func)
}
// 綁定a,a若變化則執(zhí)行回調(diào)方法
var va = {a:{c: 'c'}, b:{c: 'c'}}
vm._data.va = {a:{c: 'c'}, b:{c: 'c'}}
vm.$watch('va', () => {console.log('又成功被監(jiān)聽啦')})
vm.$data.va.a = 1
再執(zhí)行一次以上代碼,發(fā)現(xiàn)內(nèi)部的a屬性也被監(jiān)聽到了,而且屬性值變化的時候執(zhí)行了我們事先定義好的回調(diào)函數(shù)~嘻嘻嘻~
雖然實現(xiàn)了$watch的基本功能,但是和vue的源碼還是有一定的距離,特別是一些扁平化和模塊化的思想需要涉及到一些設計模式,其實我們在看源碼的時候,常常是逆著作者的思維走的,功能從簡單到復雜往往涉及到代碼的模塊化和解耦,使得代碼非常地分散,讀起來晦澀難懂,自己動手,從小功能代碼塊實現(xiàn),然后結合源碼,對比思路,慢慢豐富,也不失為一種學習源碼的方式~
下一篇將會結合源碼來淺談下vue的watcher和observer
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
vue打包通過image-webpack-loader插件對圖片壓縮優(yōu)化操作
這篇文章主要介紹了vue打包通過image-webpack-loader插件對圖片壓縮優(yōu)化操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11

