监听一个对象的变化是实现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方法