Vue響應(yīng)式系統(tǒng)基本原理
Object.defineProperty
Object.defineProperty(obj, prop, descriptor)
這是實(shí)現(xiàn)響應(yīng)式的基礎(chǔ)鞋屈,通過(guò)對(duì)象屬性描述對(duì)象來(lái)控制obj
的prop
{
value: 'static'
writable: true,
enumerable: true
configurable: true
get: function(){}
set: function(){}
}
上面六個(gè)對(duì)象屬性的描述屬性蕉世,可以看作是控制屬性的屬性
實(shí)現(xiàn)Observer
首先定義一個(gè)更新函數(shù)來(lái)模擬視圖更新
function updata (val) {
console.log("視圖更新了哈~");
}
然后定義一個(gè)defineReact
函數(shù)通過(guò)Object.defineProperty(obj, prop, descriptor)
拂苹,來(lái)進(jìn)行數(shù)據(jù)劫持砍艾,實(shí)現(xiàn)對(duì)象的響應(yīng)化霎俩。
當(dāng)一個(gè)對(duì)象的屬性一旦定義了取值函數(shù)get
(或存值函數(shù)set
)危虱,就不能將writable
屬性設(shè)為true
拌消,或者同時(shí)定義value
屬性置蜀,否則會(huì)報(bào)錯(cuò)镣煮。
經(jīng)過(guò)defineReact
函數(shù)處理后姐霍,對(duì)象讀取屬性時(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);
}
});
通過(guò)遞歸遍歷將一個(gè)obj的屬性都轉(zhuǎn)化為由存取器控制典唇,為方便理解去掉遞歸的過(guò)程
function observer (targetObj) {
if (!targetObj || (typeof targetObj !== 'object')) {
return;
}
Object.keys(targetObj).forEach((key) => {
defineReact(targetObj, key, targetObj[key]);
});
}
最后來(lái)看看在Vue中的實(shí)現(xiàn)
class Vue {
//Vue的構(gòu)造函數(shù)
constructor(options) {
this._data = options.data;
observer(this._data);
}
}
我們?cè)谑褂肰ue生成實(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ù),來(lái)更新頁(yè)面炎咖,實(shí)現(xiàn)響應(yīng)式赃泡。
響應(yīng)式系統(tǒng)的依賴(lài)收集
依賴(lài)收集會(huì)讓 data中的某個(gè)數(shù)據(jù)知道有多少個(gè)地方依賴(lài)我的數(shù)據(jù),在我變化的時(shí)候需要通知它們乘盼。
創(chuàng)建一個(gè)發(fā)布訂閱模式GatherWatcher來(lái)接收依賴(lài)數(shù)據(jù)的更新升熊,它的主要作用就是將在Vue實(shí)例中依賴(lài)data中某個(gè)數(shù)據(jù)的所有地方(watcher
)收集起來(lái),統(tǒng)一管理绸栅。
class GatherWatcher {
constructor () {
/* 用來(lái)存放Watcher對(duì)象的數(shù)組 */
this.subs = [];
}
/* 在subs中添加一個(gè)Watcher對(duì)象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher對(duì)象更新視圖 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
接下來(lái)我們修改一下 defineReact
以及 Vue 的構(gòu)造函數(shù)僚碎,來(lái)完成依賴(lài)收集。
function defineReactive (obj, key, val) {
//一個(gè)watchers對(duì)象阴幌,來(lái)收集對(duì)某個(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對(duì)象存入watchera的subs中
watchers.addSub(watcher);
return val;
},
set: function reactiveSetter (newVal) {
if (newVal === val) return;
// 在set的時(shí)候觸發(fā)watchers的notify來(lái)通知所有的watcher對(duì)象更新視圖
watchers.notify();
}
});
}
class Vue {
constructor(options) {
this._data = options.data;
observer(this._data);
/* 新建一個(gè)Watcher觀察者對(duì)象勺阐,這時(shí)候wacther會(huì)指向這個(gè)Watcher對(duì)象 */
new Watcher();
console.log('render', this._data.test);//模擬render
}
}
總結(jié)一下
1. 通過(guò)`Object.defineProperty` 把這些屬性全部轉(zhuǎn)為 `getter/setter`,進(jìn)行數(shù)據(jù)劫持
2. 在某個(gè)數(shù)據(jù)`getter`時(shí)矛双,通過(guò)訂閱渊抽,收集對(duì)這個(gè)數(shù)據(jù)的依賴(lài)者們(watcher)
3. 在數(shù)據(jù)setter時(shí),發(fā)布更新`Notify`,通知這個(gè)數(shù)據(jù)的訂閱者們進(jìn)行更新议忽,重新渲染懒闷。