数据监测是江湖行走必备之良技
自从MVVM框架从前端兴起,各种武艺秘技在前端江湖横飞四起。当今的三大“门派”——angular,vue,react在前端各站鳌头,究其内门功夫,都有数据绑定(双向或者单向)一技。angularjs的脏检查,angular2的zone.js,vue的defineProperty,以及react的单向数据流,都在试图用最低的成本让程序猿(媛)能够快速的飞檐走壁,练出app。
洒家vue数据监测依赖什么?
vue得益于es5的 Object.defineProperty方法,可以为正常的对象读取值附加一层拦截。有如火眼金睛,任何对象的一举一动都窥于目下(斯贼,休敢乱动)。
Object.defineProperty(obj,'keyName',{
//数据描述符
configurable:true/false(default) // 该属性能否被delete删除
enumerable: true/false(default) //该属性能否被枚举
value:任何JavaScript值(默认undefined) //该属性的值
writable: true/false(default) //该属性值能否被复制运算符改变
//存取描述符 --> 数据监测主要依靠
get:Function/undefined(default) //getter方法,属性被访问会触发
set:Function/undefined(default) //setter方法,属性被赋值会触发
})
//举个橘子
let obj = {};
Object.defineProperty(obj,'test',{
get:function(){
console.log('我被访问了,速度返回404');
return 404;
},
set:function(newVal){
console.log('我被赋值了,^-^,赋的值是:',newVal);
}
})
console.log(obj.test)//我被访问了,速度返回404 //404
obj.test = 200;//我被赋值了,^-^,赋的值是 200
那么当对象被赋值的时候,会触发set方法,在set里比对新值与旧值,然后做相应的处理,不就达到数据监测并处理的目的了吗?这么简单,想想还有点小激动呢。
初级版的数据监测招式
建立一个监测类,能够监测对象的变动并执行指定的回调函数
class Survey{
constructor(obj,callback){
if(Object.prototype.toString.call(obj) !== '[object Object]')
console.error('This parameter should be an Object',obj);
this.callback = callback;
this.detect(obj);
}
detect(target){
Object.keys(target).forEach(function(key,index,keyArray){
let value = target[key]; //相当于旧值
Object.defineProperty(target,key,{
get:function(){
return value;
},
set:(function(newVal){
if(value !== newVal)
{
//如果新值是个对象,会再遍历属性以监测
if(Object.prototype.toString.call(newVal) === '[object Object]')
{
this.detect(newVal);
}
this.callback(value,newVal);
value=newVal; //将新值赋予旧值
}
}).bind(this)
});
},this);
if(Object.prototype.toString.call(target[key]) === '[object Object]')
this.detect(target[key]);
}
}
//测试
let obj={a:1,b:2};
let watcher=new Survey(obj,function(old,newVal){
console.log(old);
console.log(newVal);
});
obj.b=3; // 2 3
是不是感觉很easy,哪里不会点哪里? 但是这种设计能应付监测的对象是数组的情况吗?答案是不能的。很明显数组操作如push或者unshift,以上设计并不能监测到这种举动。
升级版搞定数组监测
怎么能够侦测到数组操作带来的变化呢?数组的变化来源其方法,如果数组的方法调用附带着其值的检测,那不是就可以监测数组变化了么,那怎么让数组的方法调用时能够附带作其它处理呢?能这么干的就只能在数组的原型(prototype)上动心思了。
//数组的方法就那么几个,可以使用改写其Array.prototype让每个数组方法调用时,都能检测操作值的处理
//数组的方法还有一些
let ARR_METHODS=['push','pop','shift','unshift','splice','sort','reverse','map'];
class survey_2{
constructor(obj,callback){
if(Object.prototype.toString.call(obj) !== '[object Object]' &&
Object.prototype.toString.call(obj) !== '[object Array]')
console.error('This parameter should be an Object',obj);
this.callback=callback;
this.detect(obj)
}
detect(target){
//如果target是数组,则重写其方法
if(Object.prototype.toString.call(target) === '[object Array]')
this.overrideArrPrototype(target);
Object.keys(target).forEach(function(key,index,keyArray){
let oldVal = target[key];
Object.defineProperty(target,key,{
get:function(){
return oldVal;
},
set:(
function(newVal){
console.log(newVal);
if(newVal !== oldVal)
{
let valType=Object.prototype.toString(newVal);
if( valType === '[object Object]' ||
valType === '[object Array]')
this.detect(newVal);
this.callback(oldVal,newVal);
oldVal = newVal;
}
}
).bind(this)
})
let targetPropType = Object.prototype.toString.call(target[key]);
if( targetPropType === '[object Object]' ||
targetPropType === '[object Array]')
this.detect(target[key]);
},this);
}
overrideArrPrototype(array){
//产生一个数组原型副本,用于分配给需要重写的数组
let overrideProto=Object.create(Array.prototype);
let _this=this;
//重写数组方法
Object.keys(ARR_METHODS).forEach((key,index,keyArrary)=>{
let method=ARR_METHODS[key];
let oldArray=[];
Object.defineProperty(overrideProto,method,{
value:function(){
oldArray=this.slice(0);//存储旧数组
let args=[].slice.apply(arguments);
Array.prototype[method].apply(this,args);
_this.detect(this);
_this.callback(oldArray,this);
},
writable:true,
enumerable:false,
configurable:true
})
});
//将目标数组的原型替换成改造后的原型
array.__proto__=overrideProto;
}
}
let arr={a:1,b:[1]};
let test =new survey_2(arr,function(oldArr,newArr){
console.log(oldArr);
console.log(newArr);
})
arr.b.push(4); //[1] [1,4]
这样就愉快完成了能对对象与数组进行检测的一个监测类了,是不是so easy.
总结:vue的数据监测主要运用的是es5的defineProperty特性,对于数组的检测则通过重写其数组方法来达到检测的目的。但是进一步,有木有能检测到哪个地方改变了的方法呢? 下回拆解。