監聽一個對象的變化是實現watcher與雙向數據綁定的基礎,我們來一起看看如何監聽一個對象的變化。
在這里我們可以用到ES5中Object的defineProperty屬性來做到對一個對象進行監聽,那么先簡單認識一下defineProperty的用法。
1 let obj = {}; 2 let nameVal = 'friday'; 3 4 Object.defineProperty(obj, 'name', { 5 configurable : true,//是否是可配置的----是否可以更改enumerable,writable等 6 enumerable : true,//是否可被for in枚舉 7 get : function() { 8 return nameVal.toUpperCase(); 9 }, 10 set : function(newVal) { 11 nameVal = newVal; 12 } 13 });
這里需要注意下name並不用定義在obj自身當中,只要保證get與set函數拿到外層定義的nameVal值,即相當於obj本身定義了name屬性,如果想監控自身本身就擁有的name屬性可以如下寫法。
1 let obj = {name : 'friday'}; 2 let val = obj.name; 3 4 Object.defineProperty(obj, 'name', { 5 configurable : true, 6 enumerable : true, 7 get : function() { 8 return val.toUpperCase(); 9 //注意這里不能寫成return obj.name.toUpperCase();會造成死循環無限執行getter造成泄漏 10 }, 11 set : function(newVal) { 12 if(val === newVal) return; 13 val = newVal; 14 } 15 });
基本的監聽對象變化就如上面的代碼呈現的一樣,但是還有一個問題如果對象的屬性依然是對象,這種情況該如何處理比如下面的這種結構。
1 let obj = { 2 user : { 3 name : 'friday', 4 gender : 'male' 5 }, 6 info : { 7 age : 2 8 } 9 };
答案是我們可以使用遞歸算法來監聽每一個對象
1 var Observer = function(data) { 2 //此處的this.data = data;為了方便后面原型上拿取對象 3 this.data = data; 4 this.walk(data); 5 }; 6 7 Observer.prototype.walk = function(obj) { 8 9 let val; 10 for(let key in obj) { 11 //因為for in循環會枚舉原型鏈上的key所以用hasOwnProperty來過濾 12 if(obj.hasOwnProperty(key)) { 13 val = obj[key]; 14 if(typeof val === 'object') { 15 new Observer(val); 16 } 17 //注意此處使用一個閉包,因為如果直接使用Object.defineProperty最終返回的val值永遠是遍歷拿到的最后一個val,當然改寫的方式不止一種,我們也可以不用閉包直接利用let的特性將let寫進for in循環中,或者將這個匿名函數閉包直接定義在原型上,在此處我們推薦后一種方式 18 (function(key, val){ 19 Object.defineProperty(obj, key, { 20 configurable : true, 21 enumerable : true, 22 get : function() { 23 console.log('你訪問了' + key); 24 return val; 25 }, 26 set : function(newVal) { 27 console.log('你更改了' + key); 28 if(val === newVal) return; 29 val = newVal; 30 } 31 }); 32 })(key, val); 33 } 34 } 35 36 };
將閉包函數定義在原型中
1 Observer.prototype.walk = function (obj) { 2 3 let val; 4 for(let key in obj) { 5 if(obj.hasOwnProperty(key)) { 6 val = obj[key]; 7 if(typeof val === 'object') { 8 new Observer(val); 9 } 10 this.convert(key, val); 11 } 12 } 13 14 }; 15 16 Observer.prototype.convert = function (key, val) { 17 18 Object.defineProperty(this.data, key, { 19 configurable : true, 20 enumerable : true, 21 get : function () { 22 console.log('你訪問了' + key); 23 return val; 24 }, 25 set : function (newVal) { 26 console.log('你更改了' + key + '=' + newVal); 27 if(val === newVal) return; 28 val = newVal; 29 } 30 }) 31 32 }; 33 34 new Observer(obj);
上面的代碼遺留了兩個問題 1. 不能監聽數組的變化 2. 如果重新set的屬性是對象話,新對象不具有setter與getter方法
