Vue2?響應(yīng)式系統(tǒng)之分支切換
場(chǎng)景
我們考慮一下下邊的代碼會(huì)輸出什么。
import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
text: "hello, world",
ok: true,
};
observe(data);
const updateComponent = () => {
console.log("收到", data.ok ? data.text : "not");
};
new Watcher(updateComponent); // updateComponent 執(zhí)行一次函數(shù),輸出 hello, world
data.ok = false; // updateComponent 執(zhí)行一次函數(shù),輸出 not
data.text = "hello, liang"; // updateComponent 會(huì)執(zhí)行嗎?
我們來一步一步理清:
observer(data)
攔截了 中 和 的 ,并且各自初始化了一個(gè) 實(shí)例,用來保存依賴它們的 對(duì)象。datatextokget、setDepWatcher

new Watcher(updateComponent)
這一步會(huì)執(zhí)行 函數(shù),執(zhí)行過程中用到的所有對(duì)象屬性,會(huì)將 收集到相應(yīng)對(duì)象屬性中的 中。updateComponentWatcherDep

當(dāng)然這里的 其實(shí)是同一個(gè),所以用了指向的箭頭。Watcher
data.ok = false
這一步會(huì)觸發(fā) ,從而執(zhí)行 中所有的 ,此時(shí)就會(huì)執(zhí)行一次 。setDepWatcherupdateComponent
執(zhí)行 就會(huì)重新讀取 中的屬性,觸發(fā) ,然后繼續(xù)收集 。updateComponentdatagetWatcher

重新執(zhí)行 函數(shù) 的時(shí)候:updateComponent
const updateComponent = () => {
console.log("收到", data.ok ? data.text : "not");
};
因?yàn)?nbsp;的值變?yōu)?nbsp;,所以就不會(huì)觸發(fā) 的 , 的 就不會(huì)變化了。data.okfalsedata.textgettextDep
而 會(huì)繼續(xù)執(zhí)行,觸發(fā) 收集 ,但由于我們 中使用的是數(shù)組,此時(shí)收集到的兩個(gè) 其實(shí)是同一個(gè),這里是有問題,會(huì)導(dǎo)致 重復(fù)執(zhí)行,一會(huì)兒我們來解決下。data.okgetWatcherDepWacherupdateComponent
data.text = "hello, liang"
執(zhí)行這句的時(shí)候,會(huì)觸發(fā) 的 ,所以會(huì)執(zhí)行一次 。但從代碼來看 函數(shù)中由于 為 , 對(duì)輸出沒有任何影響,這次執(zhí)行其實(shí)是沒有必要的。textsetupdateComponentupdateComponentdata.okfalsedata.text
之所以執(zhí)行了,是因?yàn)榈谝淮螆?zhí)行 讀取了 從而收集了 ,第二次執(zhí)行 的時(shí)候, 雖然沒有讀到,但之前的 也沒有清除掉,所以這一次改變 的時(shí)候 依舊會(huì)執(zhí)行。updateComponentdata.textWatcherupdateComponentdata.textWatcherdata.textupdateComponent
所以我們需要的就是當(dāng)重新執(zhí)行 的時(shí)候,如果 已經(jīng)不依賴于某個(gè) 了,我們需要將當(dāng)前 從該 中移除掉。updateComponentWatcherDepWatcherDep

問題
總結(jié)下來我們需要做兩件事情。
- 去重, 中不要重復(fù)收集 。
DepWatcher - 重置,如果該屬性對(duì) 中的 已經(jīng)沒有影響了(換句話就是, 中的 已經(jīng)不會(huì)讀取到該屬性了 ),就將該 從該屬性的 中刪除。
DepWacherWatcherupdateComponentWatcherDep
去重
去重的話有兩種方案:
Dep中的 數(shù)組換為 。subsSet- 每個(gè) 對(duì)象引入 , 對(duì)象中記錄所有的 的 ,下次重新收集依賴的時(shí)候,如果 的 已經(jīng)存在,就不再收集該 了。
DepidWatcherDepidDepidWatcher
Vue2 源碼中采用的是方案 這里我們實(shí)現(xiàn)下:2
Dep 類的話只需要引入 即可。id
/*************改動(dòng)***************************/
let uid = 0;
/****************************************/
export default class Dep {
static target; //當(dāng)前在執(zhí)行的函數(shù)
subs; // 依賴的函數(shù)
id; // Dep 對(duì)象標(biāo)識(shí)
constructor() {
/**************改動(dòng)**************************/
this.id = uid++;
/****************************************/
this.subs = []; // 保存所有需要執(zhí)行的函數(shù)
}
addSub(sub) {
this.subs.push(sub);
}
depend() {
if (Dep.target) {
// 委托給 Dep.target 去調(diào)用 addSub
Dep.target.addDep(this);
}
}
notify() {
for (let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update();
}
}
}
Dep.target = null; // 靜態(tài)變量,全局唯一
在 中,我們引入 來記錄所有的 。Watcherthis.depIdsid
import Dep from "./dep";
export default class Watcher {
constructor(Fn) {
this.getter = Fn;
/*************改動(dòng)***************************/
this.depIds = new Set(); // 擁有 has 函數(shù)可以判斷是否存在某個(gè) id
/****************************************/
this.get();
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
Dep.target = this; // 保存包裝了當(dāng)前正在執(zhí)行的函數(shù)的 Watcher
let value;
try {
value = this.getter.call();
} catch (e) {
throw e;
} finally {
this.cleanupDeps();
}
return value;
}
/**
* Add a dependency to this directive.
*/
addDep(dep) {
/*************改動(dòng)***************************/
const id = dep.id;
if (!this.depIds.has(id)) {
dep.addSub(this);
}
/****************************************/
}
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update() {
this.run();
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run() {
this.get();
}
}
重置
同樣是兩個(gè)方案:
- 全量式移除,保存 所影響的所有 對(duì)象,當(dāng)重新收集 的前,把當(dāng)前 從記錄中的所有 對(duì)象中移除。
WatcherDepWatcherWatcherDep - 增量式移除,重新收集依賴時(shí),用一個(gè)新的變量記錄所有的 對(duì)象,之后再和舊的 對(duì)象列表比對(duì),如果新的中沒有,舊的中有,就將當(dāng)前 從該 對(duì)象中移除。
DepDepWatcherDep
Vue2 中采用的是方案 ,這里也實(shí)現(xiàn)下。2
首先是 類,我們需要提供一個(gè) 方法。DepremoveSub
import { remove } from "./util";
/*
export function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item);
if (index > -1) {
return arr.splice(index, 1);
}
}
}
*/
let uid = 0;
export default class Dep {
static target; //當(dāng)前在執(zhí)行的函數(shù)
subs; // 依賴的函數(shù)
id; // Dep 對(duì)象標(biāo)識(shí)
constructor() {
this.id = uid++;
this.subs = []; // 保存所有需要執(zhí)行的函數(shù)
}
addSub(sub) {
this.subs.push(sub);
}
/*************新增************************/
removeSub(sub) {
remove(this.subs, sub);
}
/****************************************/
depend() {
if (Dep.target) {
// 委托給 Dep.target 去調(diào)用 addSub
Dep.target.addDep(this);
}
}
notify() {
for (let i = 0, l = this.subs.length; i < l; i++) {
this.subs[i].update();
}
}
}
Dep.target = null; // 靜態(tài)變量,全局唯一
然后是 類,我們引入 來保存所有的舊 對(duì)象,引入 來保存所有的新 對(duì)象。Watcherthis.depsDepthis.newDepsDep
import Dep from "./dep";
export default class Watcher {
constructor(Fn) {
this.getter = Fn;
this.depIds = new Set(); // 擁有 has 函數(shù)可以判斷是否存在某個(gè) id
/*************新增************************/
this.deps = [];
this.newDeps = []; // 記錄新一次的依賴
this.newDepIds = new Set();
/****************************************/
this.get();
}
/**
* Evaluate the getter, and re-collect dependencies.
*/
get() {
Dep.target = this; // 保存包裝了當(dāng)前正在執(zhí)行的函數(shù)的 Watcher
let value;
try {
value = this.getter.call();
} catch (e) {
throw e;
} finally {
/*************新增************************/
this.cleanupDeps();
/****************************************/
}
return value;
}
/**
* Add a dependency to this directive.
*/
addDep(dep) {
const id = dep.id;
/*************新增************************/
// 新的依賴已經(jīng)存在的話,同樣不需要繼續(xù)保存
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
dep.addSub(this);
}
}
/****************************************/
}
/**
* Clean up for dependency collection.
*/
/*************新增************************/
cleanupDeps() {
let i = this.deps.length;
// 比對(duì)新舊列表,找到舊列表里有,但新列表里沒有,來移除相應(yīng) Watcher
while (i--) {
const dep = this.deps[i];
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this);
}
}
// 新的列表賦值給舊的,新的列表清空
let tmp = this.depIds;
this.depIds = this.newDepIds;
this.newDepIds = tmp;
this.newDepIds.clear();
tmp = this.deps;
this.deps = this.newDeps;
this.newDeps = tmp;
this.newDeps.length = 0;
}
/****************************************/
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update() {
this.run();
}
/**
* Scheduler job interface.
* Will be called by the scheduler.
*/
run() {
this.get();
}
}
測(cè)試
回到開頭的代碼
import { observe } from "./reactive";
import Watcher from "./watcher";
const data = {
text: "hello, world",
ok: true,
};
observe(data);
const updateComponent = () => {
console.log("收到", data.ok ? data.text : "not");
};
new Watcher(updateComponent); // updateComponent 執(zhí)行一次函數(shù),輸出 hello, world
data.ok = false; // updateComponent 執(zhí)行一次函數(shù),輸出 not
data.text = "hello, liang"; // updateComponent 會(huì)執(zhí)行嗎?
此時(shí) 修改的話就不會(huì)再執(zhí)行 了,因?yàn)榈诙螆?zhí)行的時(shí)候,我們把 中 里的 清除了。data.textupdateComponentdata.textDepWatcher
總結(jié)
今天這個(gè)主要就是對(duì)響應(yīng)式系統(tǒng)的一點(diǎn)優(yōu)化,避免不必要的重新執(zhí)行。所做的事情就是重新調(diào)用函數(shù)的時(shí)候,把已經(jīng)沒有關(guān)聯(lián)的 去除。Watcher

不知道看到這里大家有沒有一個(gè)疑問,我是一直沒想到說服我的點(diǎn),歡迎一起交流:
在解決去重問題上,我們是引入了 ,但如果直接用 其實(shí)就可以。在 類中是用 來存 ,用數(shù)組來存 對(duì)象,為什么不直接用 來存 對(duì)象呢?idsetWatcherSetidDepSetDep
到此這篇關(guān)于Vue2 響應(yīng)式系統(tǒng)之分支切換的文章就介紹到這了,更多相關(guān)Vue2分支切換內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決vue項(xiàng)目nginx部署到非根目錄下刷新空白的問題
今天小編就為大家分享一篇解決vue項(xiàng)目nginx部署到非根目錄下刷新空白的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-09-09
vue-element換膚所有主題色和基礎(chǔ)色均可實(shí)現(xiàn)自主配置
這篇文章主要介紹了vue-element換膚所有主題色和基礎(chǔ)色均可實(shí)現(xiàn)自主配置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04
Vue-cli集成axios請(qǐng)求出現(xiàn)CORS跨域問題及解決
這篇文章主要介紹了Vue-cli集成axios請(qǐng)求出現(xiàn)CORS跨域問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,2023-10-10
VUE登錄注冊(cè)頁(yè)面完整代碼(直接復(fù)制)
這篇文章主要給大家介紹了關(guān)于VUE登錄注冊(cè)頁(yè)面的相關(guān)資料,在Vue中可以使用組件來構(gòu)建登錄注冊(cè)頁(yè)面,文中通過圖文以及代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
詳解Vue項(xiàng)目引入CreateJS的方法(親測(cè)可用)
CreateJS是基于HTML5開發(fā)的一套模塊化的庫(kù)和工具。這篇文章主要介紹了Vue項(xiàng)目引入CreateJS的方法(親測(cè)),需要的朋友可以參考下2019-05-05
vue實(shí)現(xiàn)搜索并高亮文字的兩種方式總結(jié)
mpvue中配置vuex并持久化到本地Storage圖文教程解析

