資料監測是江湖行走必備之良技
自從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特性,對於陣列的檢測則通過重寫其陣列方法來達到檢測的目的。但是進一步,有木有能檢測到哪個地方改變了的方法呢? 下回拆解。