Vue 提供了一種通用的方式來觀察和響應 Vue 實例上的數據變更:監聽屬性 watch。小程序
雖然watch的濫用會致使性能不佳,但在一些狀況下咱們仍是須要watch,使得代碼更加簡潔、邏輯更加清晰(其實就是嫌麻煩...)。微信小程序
接下來我將逐步講解微信小程序中如何實現一個監聽器 watch,若想直接看最終代碼,可直接滑動至底部。數組
監聽器的原理,將data中需監聽的屬性寫在watch對象中,並給其提供一個方法,當被監聽屬性的值改變時,調用該方法。服務器
因此很顯然,咱們須要用到Javascript中的Object.defineProperty()方法,來手動劫持對象的getter/setter,從而實現給對象賦值時(調用setter),執行watch對象中相對應的函數,達到監聽效果。Object.defineProperty()不在這裏詳細介紹,還不會使用的童鞋速戳這裏-> Object.defineProperty()介紹。微信
首先,既然是微信小程序自定義watch屬性,我建議直接將代碼寫在app.js內,須要使用的頁面直接在onLoad()內調用getApp().setWatch(...)便可app
// app.js /** * 小程序初始化 */ onLaunch(){...} /** * 設置監聽器 */ setWatcher(data, watch) { // 接收index.js傳過來的data對象和watch對象 Object.keys(watch).forEach(v => { // 將watch對象內的key遍歷 this.observe(data, v,watch[v]); // 監聽data內的v屬性,傳入watch內對應函數以調用 }) }, /** * 監聽屬性 並執行監聽函數 */ observe(obj, key,watchFun) { var val = obj[key]; // 給該屬性設默認值 Object.defineProperty(obj, key, { configurable: true, enumerable: true, set: function(value) { val = value; watchFun(value,val); // 賦值(set)時,調用對應函數 }, get: function() { return val; } }) } 此時在index.js中已經能夠實現簡單的屬性監聽: //index.js Page({ data: { name:"xuyang" }, onLoad(){ getApp().setWatcher(this.data, this.watch); // 設置監聽器 this.setData({ name:'lxm' }) }, watch:{ name:function(newValue){ console.log(newValue); // name改變時,調用該方法輸出新值。 } } })
咱們能夠看到,index頁面啓動後,name被賦值成'lxm',同時觸發其setter,調用watch內的name()方法,控制檯打印出 lxm。函數
是否是挺簡單的呢?掌握了這一點,接下來的事情就好辦多了。性能
咱們知道,Vue中 watch對象內不光是能寫name、age這類屬性,還能用引號寫出'my.name'、'my.age'這類屬性,即對my對象下的name、age進行監聽。 咱們只須要改進一下setWatcher方法:this
/** * 設置監聽器 */ setWatcher(data, watch) { Object.keys(watch).forEach(v => { let key = v.split('.'); // 將watch中的屬性以'.'切分紅數組 let nowData = data; // 將data賦值給nowData for (let i = 0; i < key.length - 1; i++) { // 遍歷key數組的元素,除了最後一個! nowData = nowData[key[i]]; // 將nowData指向它的key屬性對象 } let lastKey = key[key.length-1]; // 假設key==='my.name',此時nowData===data['my']===data.my,lastKey==='name' this.observe(nowData, lastKey,watch[v]); // 監聽nowData對象的lastKey }) } 此時在index.js中能夠這樣寫: //index.js Page({ data: { my:{ name:"xuyang", age:21 } }, onLoad(){ getApp().setWatcher(this.data, this.watch); this.data.my.name = 'lxm'; this.data.my.age = 2; this.setData({ my: this.data.my }) }, watch:{ 'my.name':function(newValue){ console.log(newValue); }, 'my.age':function(newValue){ console.log(newValue); } } })
控制檯會打印出 lxm 和 2spa
如今咱們又離成功更進了一步~ 接下來咱們要實現Vue中的深度監聽,即監聽my對象便可監聽它的內部全部屬性以及屬性的屬性以及屬性的屬性的屬性......而且監聽函數的this要指向這個page裏的this。
須要深度監聽,Vue中的寫法是
watch:{ my:{ handler(newValue){ // do something... }, deep:true // 深度監聽 } } 咱們須要修改一下app.js中的兩個函數: /** * 設置監聽器 */ setWatcher(page) { let data = page.data; let watch = page.watch; Object.keys(watch).forEach(v => { let key = v.split('.'); // 將watch中的屬性以'.'切分紅數組 let nowData = data; // 將data賦值給nowData for (let i = 0; i < key.length - 1; i++) { // 遍歷key數組的元素,除了最後一個! nowData = nowData[key[i]]; // 將nowData指向它的key屬性對象 } let lastKey = key[key.length - 1]; // 假設key==='my.name',此時nowData===data['my']===data.my,lastKey==='name' let watchFun = watch[v].handler || watch[v]; // 兼容帶handler和不帶handler的兩種寫法 let deep = watch[v].deep; // 若未設置deep,則爲undefine this.observe(nowData, lastKey, watchFun, deep, page); // 監聽nowData對象的lastKey }) }, /** * 監聽屬性 並執行監聽函數 */ observe(obj, key, watchFun, deep, page) { var val = obj[key]; // 判斷deep是true 且 val不能爲空 且 typeof val==='object'(數組內數值變化也須要深度監聽) if (deep && val != null && typeof val === 'object') { Object.keys(val).forEach(childKey=>{ // 遍歷val對象下的每個key this.observe(val,childKey,watchFun,deep,page); // 遞歸調用監聽函數 }) } var that = this; Object.defineProperty(obj, key, { configurable: true, enumerable: true, set: function(value) { // 用page對象調用,改變函數內this指向,以便this.data訪問data內的屬性值 watchFun.call(page,value,val); // value是新值,val是舊值 val = value; if(deep){ // 如果深度監聽,從新監聽該對象,以便監聽其屬性。 that.observe(obj, key, watchFun, deep, page); } }, get: function() { return val; } }) } 其中須要特別注意,如果data中先聲明一個對象userInfo = {},再給userInfo賦值一個對象(好比從服務器獲取用戶數據),則須要從新遍歷監聽才能實現深度監聽,故需加上這段代碼: if(deep){ // 如果深度監聽,從新監聽該對象,以便監聽其屬性。 that.observe(obj, key, watchFun, deep); } 此時index.js中須要深度監聽的屬性,能夠這樣寫: //index.js Page({ data: { my: { name: 'xuyang', age: 21, hobby: ['girls', 'games'] }, nameInfo:{} }, onLoad() { getApp().setWatcher(this); this.data.my.hobby[0] = 'study'; this.setData({ nameInfo:{name:'haha',sex:'boy'} }) console.log(this.data) }, watch: { my:{ handler(newValue) { console.log(newValue); }, deep:true } } })