原理:vue.js采用的是數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式,通過Object.defineProperty()來劫持各個屬性的setter,getter屡拨,在數(shù)據(jù)變動時發(fā)布消息給訂閱者幕垦,觸發(fā)相應(yīng)的監(jiān)聽回調(diào)吼具。
一:數(shù)據(jù)劫持
在ES5中有Object.defineProperty()方法赡勘,它能監(jiān)聽各個屬性的set和get方法哟冬。
let data ={name:'px'}
Object.defineProperty(data,'name',{
set: function(newValue) {
console.log('更新了data的name:' + newValue);
},
get: function() {
console.log('獲取data數(shù)據(jù)name');
}
})
data.name="sj";//更新了data的name:sj
data.name;//獲取data數(shù)據(jù)name
當執(zhí)行"data.name='sj'"觸發(fā)set方法楼熄,執(zhí)行"data.name"觸發(fā)get方法。
vue就是采用了此方法浩峡,對data的屬性進行劫持可岂。
模擬實現(xiàn)其劫持的過程如下:
let data ={name:'px',age:'20'}
function observe(data){
//獲取所有的data數(shù)據(jù)對象中的所有屬性進行遍歷
//Object.keys() 方法會返回一個由一個給定對象的自身可枚舉屬性組成的數(shù)組
const keys = Object.keys(data)
for (let i = 0; i < keys.length; i++) {
let val = data[keys[I]];
defineReactive(data, keys[i],val)//為每個屬性增加監(jiān)聽
}
}
function defineReactive(obj,key,val){
Object.defineProperty(obj, key, {
enumerable: true,//可枚舉
configurable: true,//可配置
get: function reactiveGetter () {
//模擬get劫持
console.log("get劫持");
return val;
},
set: function reactiveSetter (newVal) {
//模擬set劫持
console.log("set劫持,新值:"+newVal);
val = newVal;
}
})
}
observe(data);
data.name="sj";//set劫持,新值:sj
console.log(data.name);//get劫持,sj
data模擬vue.data對象,observer中對data的屬性進行遍歷翰灾,調(diào)用defineReactive方法對每個屬性的get和set方法進行劫持缕粹。
那么監(jiān)聽數(shù)據(jù)變化后如何通知呢,請看下面纸淮。
二:發(fā)布與訂閱
vue在雙向綁定的設(shè)計中平斩,采用的是觀察者-訂閱者模式,前面所講的數(shù)據(jù)劫持咽块,其實就是為屬性創(chuàng)建了一個觀察者對象绘面,監(jiān)聽數(shù)據(jù)的變化。
屬性發(fā)生變化了侈沪,就需要告訴訂閱者Watcher看是否需要更新揭璃。因為訂閱者是有很多個,所以需要有一個消息訂閱器Dep來專門收集這些訂閱者峭竣,然后在監(jiān)聽器Observer和訂閱者Watcher之間進行統(tǒng)一管理的塘辅。還需要有一個指令解析器Compile,對每個節(jié)點元素進行掃描和解析皆撩,將相關(guān)指令對應(yīng)初始化成一個訂閱者Watcher扣墩,并替換模板數(shù)據(jù)或者綁定相應(yīng)的函數(shù)哲银,此時當訂閱者Watcher接收到相應(yīng)屬性的變化,就會執(zhí)行對應(yīng)的更新函數(shù)呻惕,從而更新視圖荆责。