
Vue響應(yīng)式系統(tǒng)基本原理
Object.defineProperty
Object.defineProperty(obj, prop, descriptor)
這是實(shí)現(xiàn)響應(yīng)式的基礎(chǔ),通過對象屬性描述對象來控制obj的prop
{
value: 'static'
writable: true,
enumerable: true
configurable: true
get: function(){}
set: function(){}
}
上面六個(gè)對象屬性的描述屬性,可以看作是控制屬性的屬性
實(shí)現(xiàn)Observer
首先定義一個(gè)更新函數(shù)來模擬視圖更新
function updata (val) {
console.log("視圖更新了哈~");
}
然后定義一個(gè)defineReact函數(shù)通過Object.defineProperty(obj, prop, descriptor),來進(jìn)行數(shù)據(jù)劫持,實(shí)現(xiàn)對象的響應(yīng)化。
當(dāng)一個(gè)對象的屬性一旦定義了取值函數(shù)get(或存值函數(shù)set),就不能將writable屬性設(shè)為true,或者同時(shí)定義value屬性,否則會(huì)報(bào)錯(cuò)。
經(jīng)過defineReact函數(shù)處理后,對象讀取屬性時(shí)會(huì)觸發(fā)get,修改屬性值時(shí)會(huì)觸發(fā)set
function defineReact (obj, key, value) {
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactGetter () {
return value;
},
set: function reactSetter (newValue) {
if (newValue === value) return;
updata(newValue);
}
});
通過遞歸遍歷將一個(gè)obj的屬性都轉(zhuǎn)化為由存取器控制,為方便理解去掉遞歸的過程
function observer (targetObj) {
if (!targetObj || (typeof targetObj !== 'object')) {
return;
}
Object.keys(targetObj).forEach((key) => {
defineReact(targetObj, key, targetObj[key]);
});
}
最后來看看在Vue中的實(shí)現(xiàn)
class Vue {
//Vue的構(gòu)造函數(shù)
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
我們在使用Vue生成實(shí)例的時(shí)候,就會(huì)將data中的數(shù)據(jù)實(shí)現(xiàn)Observe,響應(yīng)化
const obj = new Vue({
data: {
text: 'hello'
}
})
總結(jié),一旦觸發(fā)setter函數(shù),Vue將會(huì)自動(dòng)判斷是否要觸發(fā)視圖更新函數(shù),來更新頁面,實(shí)現(xiàn)響應(yīng)式。
響應(yīng)式系統(tǒng)的依賴收集
依賴收集會(huì)讓 data中的某個(gè)數(shù)據(jù)知道有多少個(gè)地方依賴我的數(shù)據(jù),在我變化的時(shí)候需要通知它們。
創(chuàng)建一個(gè)發(fā)布訂閱模式GatherWatcher來接收依賴數(shù)據(jù)的更新,它的主要作用就是將在Vue實(shí)例中依賴data中某個(gè)數(shù)據(jù)的所有地方(watcher)收集起來,統(tǒng)一管理。
class GatherWatcher {
constructor () {
/* 用來存放Watcher對象的數(shù)組 */
this.subs = [];
}
/* 在subs中添加一個(gè)Watcher對象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher對象更新視圖 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
接下來我們修改一下 defineReact 以及 Vue 的構(gòu)造函數(shù),來完成依賴收集。
function defineReactive (obj, key, val) {
//一個(gè)watchers對象,來收集對某個(gè)數(shù)據(jù)的的watcher
const watchers = new GatherWatcher();
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactGetter () {
//在get的時(shí)候?qū)?dāng)前的Watcher對象存入watchera的subs中
watchers.addSub(watcher);
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
// 在set的時(shí)候觸發(fā)watchers的notify來通知所有的watcher對象更新視圖
watchers.notify();
}
});
}
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一個(gè)Watcher觀察者對象,這時(shí)候wacther會(huì)指向這個(gè)Watcher對象 */
new Watcher();
console.log('render', this._data.test);//模擬render
}
}
總結(jié)一下
1. 通過`Object.defineProperty` 把這些屬性全部轉(zhuǎn)為 `getter/setter`,進(jìn)行數(shù)據(jù)劫持
2. 在某個(gè)數(shù)據(jù)`getter`時(shí),通過訂閱,收集對這個(gè)數(shù)據(jù)的依賴者們(watcher)
3. 在數(shù)據(jù)setter時(shí),發(fā)布更新`Notify`,通知這個(gè)數(shù)據(jù)的訂閱者們進(jìn)行更新,重新渲染。