很多人感覺vue2的響應(yīng)式其實(shí)用到了觀察者+發(fā)布訂閱。我們先來看一下簡(jiǎn)單的發(fā)布訂閱的代碼:
// 調(diào)度中心
class Dep {
static subscribes = {}
// 訂閱所有需求
static subscribe (key, demand) {
// 對(duì)需求分類收集
if (!Dep.subscribes[key]) Dep.subscribes[key] = []
Dep.subscribes[key].push(demand)
}
// 對(duì)所有訂閱者發(fā)布通知
static publish (key, age) {
if (!Dep.subscribes[key]) return
for (const demand of Dep.subscribes[key]) {
demand(age)
}
}
}
// 找對(duì)象的獵手類
class Watcher {
constructor (name, age) {
this.name = name // 名字
this.age = age // 年齡
}
// 訂閱,由調(diào)度中心將獵手需求分類并存放到全局
subscribe (key, demand) {
Dep.subscribe(key, demand)
}
// 發(fā)布,由調(diào)度中心將同分類下的需求全部觸發(fā)
publish (key, age) {
Dep.publish(key, age)
}
}
// 獵手注冊(cè)
const aa = new Watcher('aa', 18)
const bb = new Watcher('bb', 20)
// 獵手訂閱自己感興趣的人
aa.subscribe('key', function (age) {
if (age === aa.age) console.log(`我是aa,我們都是${age}`)
else console.log(`我是aa,我們年齡不同`)
})
bb.subscribe('key', function (age) {
if (age === bb.age) console.log(`我是bb,我們都是${age}`)
else console.log(`我是bb,我們年齡不同`)
})
// 紅娘注冊(cè)
const red = new Watcher('red', 35)
// 紅娘發(fā)布信息
red.publish('key', 20)
// 我是aa,我們年齡不同
// 我是bb,我們都是20
從上面中發(fā)現(xiàn)一個(gè)重要的點(diǎn),發(fā)布者和訂閱者是根據(jù)key值來區(qū)分的,然后通過消息中心來中轉(zhuǎn)的,他們家是是實(shí)現(xiàn)不知道對(duì)方是誰(shuí)。
而觀察者模式中觀察者是一開始就知道自己觀察的是誰(shuí)。
上面其實(shí)就是簡(jiǎn)易版的vue原理中發(fā)布訂閱那段,我們接下來看完整過程。
Vue2 的響應(yīng)式
- 創(chuàng)建一個(gè) Observer 對(duì)象,它的主要作用是給對(duì)象的每個(gè)屬性添加 getter 和 setter 方法。
- 在 getter 和 setter 方法中分別進(jìn)行依賴的收集和派發(fā)更新。
- 創(chuàng)建 Watcher 對(duì)象,用于監(jiān)聽數(shù)據(jù)的變化,當(dāng)數(shù)據(jù)發(fā)生任何變化時(shí),Watcher 對(duì)象會(huì)觸發(fā)自身的回調(diào)函數(shù)。
- 在模板解析階段,對(duì)模板中使用到的數(shù)據(jù)進(jìn)行依賴的收集,即收集 Watcher 對(duì)象。
- 當(dāng)數(shù)據(jù)發(fā)生變化時(shí),Observer 對(duì)象會(huì)通知 Dep 對(duì)象調(diào)用 Watcher 對(duì)象的回調(diào)函數(shù)進(jìn)行更新操作,即派發(fā)更新。
- 更新完畢后,Vue2 會(huì)進(jìn)行視圖的重新渲染,從而實(shí)現(xiàn)響應(yīng)式。
下面是一個(gè)基于 Object.defineProperty 實(shí)現(xiàn)響應(yīng)式的示例,僅供參考:
function observe(obj) {
if (!obj || typeof obj !== 'object') {
return;
}
Object.keys(obj).forEach(key => {
// 嘗試遞歸處理
observe(obj[key]);
let val = obj[key];
const dep = new Dep(); // 新建一個(gè)依賴
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if (Dep.target) {
dep.depend(); // 收集依賴
}
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
dep.notify(); // 派發(fā)更新
}
});
});
}
// 依賴類
class Dep {
constructor() {
this.subs = [];
}
addSub(sub) {
this.subs.push(sub);
}
removeSub(sub) {
const index = this.subs.indexOf(sub);
if (index !== -1) {
this.subs.splice(index, 1);
}
}
depend() {
if (Dep.target) {
Dep.target.addDep(this);
}
}
notify() {
this.subs.forEach(sub => sub.update());
}
}
Dep.target = null;
// 觀察者類
class Watcher {
constructor(vm, expOrFn, callback) {
this.vm = vm;
this.getter = parsePath(expOrFn);
this.callback = callback;
this.value = this.get(); // 初始化,觸發(fā)依賴
}
get() {
Dep.target = this; // 設(shè)置當(dāng)前依賴
const value = this.getter.call(this.vm, this.vm); // 觸發(fā) getter
Dep.target = null; // 清除當(dāng)前依賴
return value;
}
addDep(dep) {
dep.addSub(this);
}
update() {
const oldValue = this.value;
this.value = this.get(); // 重新獲取
this.callback.call(this.vm, this.value, oldValue); // 觸發(fā)回調(diào)
}
}
// 解析路徑
function parsePath(expOrFn) {
if (typeof expOrFn === 'function') {
return expOrFn;
}
const segments = expOrFn.split('.');
return function(obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) {
return;
}
obj = obj[segments[i]];
}
return obj;
};
}
// 測(cè)試
const obj = { foo: 'foo', bar: { a: 1 } };
observe(obj);
new Watcher(obj, 'foo', (val, oldVal) => {
console.log(`foo changed from ${oldVal} to ${val}`);
});
new Watcher(obj, 'bar.a', (val, oldVal) => {
console.log(`bar.a changed from ${oldVal} to ${val}`);
});
obj.foo = 'FOO'; // 輸出 `foo changed from foo to FOO`
obj.bar.a = 2; // 輸出 `bar.a changed from 1 to 2`
以上代碼中,函數(shù) observe 用于遞歸遍歷對(duì)象屬性,把其進(jìn)行劫持,包括收集依賴和派發(fā)更新;類 Dep 代表一個(gè)依賴,其中 addSub 用于添加訂閱者實(shí)例,removeSub 用于移除訂閱者實(shí)例,depend 用于收集依賴,即把當(dāng)前依賴加到對(duì)應(yīng)的訂閱者中,notify 用于派發(fā)更新,即遍歷所有訂閱者,并觸發(fā)其回調(diào)函數(shù)。類 Watcher 則代表一個(gè)訂閱者,其中 getter 用于獲取數(shù)據(jù),callback 用于回調(diào)函數(shù),addDep 用于添加依賴,即把當(dāng)前訂閱者添加到對(duì)應(yīng)的依賴中,update 用于更新值,并觸發(fā)相應(yīng)的回調(diào)函數(shù),如有必要。函數(shù) parsePath 則用于解析路徑字符串,返回對(duì)應(yīng)屬性的值。
例子中我們對(duì)對(duì)象 obj 進(jìn)行了劫持,同時(shí)創(chuàng)建了兩個(gè)觀察者,分別對(duì)應(yīng) foo 和 bar.a 兩個(gè)屬性。當(dāng)其中任意一個(gè)屬性的值發(fā)生變化時(shí),其對(duì)應(yīng)的依賴都會(huì)被更新,從而觸發(fā)其綁定的觀訂閱者的回調(diào)函數(shù)。
簡(jiǎn)單來說,在 Vue2 響應(yīng)式系統(tǒng)中,當(dāng)數(shù)據(jù)發(fā)生改變時(shí),會(huì)觸發(fā) get 和 set 方法,get 方法會(huì)收集所有依賴該數(shù)據(jù)的 Watcher 對(duì)象,set 方法會(huì)通知 Dep 對(duì)象觸發(fā)所有 Watcher 對(duì)象的回調(diào)函數(shù)進(jìn)行更新。如此循環(huán),實(shí)現(xiàn)了數(shù)據(jù)的響應(yīng)式。文章來源:http://www.zghlxwxcb.cn/news/detail-851318.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-851318.html
到了這里,關(guān)于vue2響應(yīng)式原理----發(fā)布訂閱模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!