如何監聽一個對象的變化


監聽一個對象的變化是實現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方法

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM