首先理解響應(yīng)式邏輯
- 知道收集視圖依賴了哪些數(shù)據(jù)
- 感知被依賴數(shù)據(jù)的變化
- 數(shù)據(jù)變化時(shí)淤毛,自動(dòng)“通知”需要更新的視圖部分,并進(jìn)行更新
01.簡(jiǎn)單編寫響應(yīng)式手動(dòng)收集依賴
const obj = {
name: "james",
age: 38,
};
//定義一個(gè)函數(shù)漫蛔,專門執(zhí)行響應(yīng)式函數(shù)
const reactiveFns = [];
function watchFn(fn) {
reactiveFns.push(fn);
fn();
//函數(shù)保存起來(lái),默認(rèn)執(zhí)行一次
//類似于wachEeffct()旧蛾,自動(dòng)收集依賴莽龟,立即執(zhí)行執(zhí)行一次
}
//凡是傳進(jìn)watchFn函數(shù)里面就是響應(yīng)式的
watchFn(function foo() {
console.log("foo name is", obj.name);
console.log("foo age is ", obj.age);
});
watchFn(function bar() {
console.log("bar name is", obj.name + "kobe");
console.log("bar age is ", obj.age + 10);
});
console.log("監(jiān)聽(tīng)age發(fā)生變化~~~~~~~~");
obj.age += 100;
//把之前依賴的函數(shù)遍歷出來(lái)執(zhí)行
reactiveFns.forEach((fn) => {
fn();
});
//目前存在缺點(diǎn),
// + 在實(shí)際開(kāi)發(fā)中锨天,響應(yīng)式對(duì)象肯定不止一個(gè)
// + 目前還不能自動(dòng)收集依賴
02.對(duì)之前的版本改進(jìn)毯盈,創(chuàng)建一個(gè)類Depend,用類去管理所有收集的函數(shù)
class Depend {
constructor() {
this.reactiveFns = [];
}
//添加對(duì)應(yīng)的依賴
addDepend(fn) {
if (fn) {
this.reactiveFns.push(fn);
}
}
//通知數(shù)據(jù)發(fā)生變化病袄,收集依賴
notify() {
this.reactiveFns.forEach((fn) => {
fn();
});
}
}
這樣在使用的時(shí)候只需要new Depend()調(diào)用即可
const dep = new Depend();
function watchFn(fn) {
dep.addDepend(fn);
fn();
//函數(shù)保存起來(lái)搂赋,默認(rèn)執(zhí)行一次
//類似于wachEeffct(),自動(dòng)收集依賴益缠,立即執(zhí)行執(zhí)行一次
}
//當(dāng)obj的值發(fā)生改變時(shí)-----
//手動(dòng)更新通知
dep.notify();
// 目前存在的缺點(diǎn):
// + obj值發(fā)生變化脑奠,還是需要手動(dòng)去調(diào)用notify收集依賴
03.監(jiān)聽(tīng)屬性變量,自動(dòng)去收集依賴更新
-
監(jiān)聽(tīng)屬性變量
到這一步時(shí)候幅慌,可以用vue2的obj.definproperty()數(shù)據(jù)劫持,或者vue3de new Proxy()去監(jiān)聽(tīng)對(duì)象屬性
//方案一宋欺。Object.defineProperty() -> Vue2
//對(duì)obj里面的數(shù)據(jù)進(jìn)行遍歷
Object.keys(obj).forEach((key) => {
let value = obj[key];
//對(duì)每個(gè)key和set操作進(jìn)行對(duì)應(yīng)劫持
Object.defineProperty(obj, key, {
set: function (newValue) {
//保存value值
value = newValue;
//去通知更新
dep.notify();
},
get: function () {
return value;
},
});
});
目前是對(duì)象屬性發(fā)生改變,不用再去手動(dòng)調(diào)用notify()更新
但是目前實(shí)現(xiàn)到這里,出現(xiàn)一個(gè)錯(cuò)誤情況迄靠,那就是收集依賴既收集name的秒咨,又同時(shí)收集age的。如果只改變name的值掌挚,age的依賴也同時(shí)更新發(fā)生變化了雨席,這種做法是錯(cuò)誤的。
想要實(shí)現(xiàn)的吠式,是在當(dāng)前屬性發(fā)生變化的時(shí)候陡厘,收集依賴只能是當(dāng)前屬性的依賴,通知更新也應(yīng)當(dāng)前屬性的更新特占,所以這里需要解決這個(gè)問(wèn)題糙置,應(yīng)該是一一對(duì)應(yīng)的關(guān)系,自動(dòng)去收集依賴
-
- dep對(duì)象數(shù)據(jù)結(jié)構(gòu)的管理
- 每一個(gè)對(duì)象的每一個(gè)屬性都會(huì)對(duì)應(yīng)一個(gè)dep對(duì)象
- 同一個(gè)對(duì)象的多個(gè)屬性的dep對(duì)象是存放一個(gè)map對(duì)象中
- 多個(gè)對(duì)象的map對(duì)象, 會(huì)被存放到一個(gè)objMap的對(duì)象中
- 依賴收集: 當(dāng)執(zhí)行g(shù)et函數(shù), 自動(dòng)的添加fn函數(shù)
所以這種做法是不要的是目。
-
重新封裝執(zhí)行自動(dòng)收集依賴
//定義一個(gè)函數(shù)谤饭,專門執(zhí)行響應(yīng)式函數(shù)
//形成閉包
let reactiveFn = null;
function watchFn(fn) {
//一旦執(zhí)行函數(shù),將值賦值給reactiveFn
reactiveFn = fn;
fn();
//函數(shù)保存起來(lái)懊纳,默認(rèn)執(zhí)行一次
//類似于wachEeffct()揉抵,自動(dòng)收集依賴,立即執(zhí)行執(zhí)行一次
reactiveFn = null;
//執(zhí)行完操作之后,賦值為null,有可能對(duì)后續(xù)操作造成影響
}
//封裝一個(gè)函數(shù)烁试,來(lái)獲取對(duì)應(yīng)的depend對(duì)象
//利用weakMap,弱引用,即使obj為null戏罢,也不能對(duì)后續(xù)造成影響,
const objMap = new WeakMap();
function getDepend(obj, key) {
//根據(jù)對(duì)象obj脚囊,找到對(duì)應(yīng)的map對(duì)象
let map = objMap.get(obj);
if (!map) {
//weakMap的key必須要是對(duì)象龟糕,所以這里用Map()
//如果拿不到,我們就new一個(gè)
map = new Map();
objMap.set(obj, map);
}
//根據(jù)key,找到對(duì)應(yīng)的depend對(duì)象
let dep = map.get(key);
if (!dep) {
dep = new Depend();
map.set(key, dep);
}
//所以一定可以返回拿到一個(gè)dep對(duì)象
return dep;
}
//方案一悔耘。Object.defineProperty() -> Vue2
//對(duì)obj里面的數(shù)據(jù)進(jìn)行遍歷
Object.keys(obj).forEach((key) => {
let value = obj[key];
//對(duì)每個(gè)key和set操作進(jìn)行對(duì)應(yīng)劫持
Object.defineProperty(obj, key, {
set: function (newValue) {
//保存value值
value = newValue;
const dep = getDepend(obj, key);
//去通知更新
dep.notify();
},
get: function () {
// 拿到obj -> key
// 找到對(duì)應(yīng)的obj對(duì)象的key對(duì)應(yīng)的dep對(duì)象
const dep = getDepend(obj, key);
//間接的把函數(shù)添加進(jìn)去
dep.addDepend(reactiveFn);
return value;
},
});
});
此時(shí)已經(jīng)完成可以自動(dòng)收集依賴更新讲岁,但是還存在倆個(gè)問(wèn)題:
- 如果監(jiān)聽(tīng)重復(fù)obj.age的代碼,它會(huì)重復(fù)執(zhí)行一遍
watchFn(function() {
console.log(obj.name)
console.log(obj.age)
console.log(obj.age)
})
- 只是只是針對(duì)單個(gè)對(duì)象淮逊,還不能針對(duì)多對(duì)象
04.最后完善優(yōu)化催首,封裝函數(shù)針對(duì)多對(duì)象調(diào)用
- 針對(duì)于重復(fù)執(zhí)行問(wèn)題,可以在類Depend里面進(jìn)行if判斷泄鹏,或者直接使用Set()
constructor() {
//set不允許重復(fù)
this.reactiveFns = new Set();
}
-
針對(duì)多對(duì)象使用問(wèn)題郎任,
-
封裝成一個(gè)reactive函數(shù)返回出去,讓外面的對(duì)象在使用的時(shí)包裹一層reactive()
function reactive(obj) { Object.keys(obj).forEach((key) => { let value = obj[key]; //對(duì)每個(gè)key和set操作進(jìn)行對(duì)應(yīng)劫持 Object.defineProperty(obj, key, { set: function (newValue) { //保存value值 value = newValue; const dep = getDepend(obj, key); //去通知更新 dep.notify(); }, get: function () { // 拿到obj -> key // 找到對(duì)應(yīng)的obj對(duì)象的key對(duì)應(yīng)的dep對(duì)象 const dep = getDepend(obj, key); //間接的把函數(shù)添加進(jìn)去 dep.depend(); return value; }, }); }); return obj; } //在使用時(shí) const obj = reactive({ name: "james", age: 38, });
-
到這一步已實(shí)現(xiàn)vue2的響應(yīng)式原理备籽,實(shí)現(xiàn)vue3的響應(yīng)式原理那就太簡(jiǎn)單舶治,直接把Object.defineProperty換成Proxy去實(shí)現(xiàn)就行了
- vue2與vue3響應(yīng)式原理的區(qū)別:
- Vue2采?數(shù)據(jù)劫持并結(jié)合了發(fā)布者-訂閱者模式分井,通過(guò) Object.defineProperty()來(lái)劫持各個(gè)屬性setter,getter霉猛,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者尺锚,觸發(fā)相應(yīng)的監(jiān)聽(tīng)回調(diào)
- Vue3放棄了Object.defineProperty API,?使?了更快的Proxy API惜浅。Proxy 是在 ES6 中引?瘫辩,它允許你攔截對(duì)該對(duì)象的任何交互,也可以避免 Vue 早期版本中存在的?些響應(yīng)性問(wèn)題坛悉。
05.寫好的最終代碼:
- vue3響應(yīng)式原理:http://www.reibang.com/p/5ef158a8e202?v=1668674511967
- vue2響應(yīng)式原理:http://www.reibang.com/p/33fe61d97fd7?v=1668674704775