Vue2?響應(yīng)式系統(tǒ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í)例,用來保存依賴它們的 對象。data
text
ok
get、set
Dep
Watcher
new Watcher(updateComponent)
這一步會(huì)執(zhí)行 函數(shù),執(zhí)行過程中用到的所有對象屬性,會(huì)將 收集到相應(yīng)對象屬性中的 中。updateComponent
Watcher
Dep
當(dāng)然這里的 其實(shí)是同一個(gè),所以用了指向的箭頭。Watcher
data.ok = false
這一步會(huì)觸發(fā) ,從而執(zhí)行 中所有的 ,此時(shí)就會(huì)執(zhí)行一次 。set
Dep
Watcher
updateComponent
執(zhí)行 就會(huì)重新讀取 中的屬性,觸發(fā) ,然后繼續(xù)收集 。updateComponent
data
get
Watcher
重新執(zhí)行 函數(shù) 的時(shí)候:updateComponent
const updateComponent = () => { console.log("收到", data.ok ? data.text : "not"); };
因?yàn)?nbsp;的值變?yōu)?nbsp;,所以就不會(huì)觸發(fā) 的 , 的 就不會(huì)變化了。data.ok
false
data.text
get
text
Dep
而 會(huì)繼續(xù)執(zhí)行,觸發(fā) 收集 ,但由于我們 中使用的是數(shù)組,此時(shí)收集到的兩個(gè) 其實(shí)是同一個(gè),這里是有問題,會(huì)導(dǎo)致 重復(fù)執(zhí)行,一會(huì)兒我們來解決下。data.ok
get
Watcher
Dep
Wacher
updateComponent
data.text = "hello, liang"
執(zhí)行這句的時(shí)候,會(huì)觸發(fā) 的 ,所以會(huì)執(zhí)行一次 。但從代碼來看 函數(shù)中由于 為 , 對輸出沒有任何影響,這次執(zhí)行其實(shí)是沒有必要的。text
set
updateComponent
updateComponent
data.ok
false
data.text
之所以執(zhí)行了,是因?yàn)榈谝淮螆?zhí)行 讀取了 從而收集了 ,第二次執(zhí)行 的時(shí)候, 雖然沒有讀到,但之前的 也沒有清除掉,所以這一次改變 的時(shí)候 依舊會(huì)執(zhí)行。updateComponent
data.text
Watcher
updateComponent
data.text
Watcher
data.text
updateComponent
所以我們需要的就是當(dāng)重新執(zhí)行 的時(shí)候,如果 已經(jīng)不依賴于某個(gè) 了,我們需要將當(dāng)前 從該 中移除掉。updateComponent
Watcher
Dep
Watcher
Dep
問題
總結(jié)下來我們需要做兩件事情。
- 去重, 中不要重復(fù)收集 。
Dep
Watcher
- 重置,如果該屬性對 中的 已經(jīng)沒有影響了(換句話就是, 中的 已經(jīng)不會(huì)讀取到該屬性了 ),就將該 從該屬性的 中刪除。
Dep
Wacher
Watcher
updateComponent
Watcher
Dep
去重
去重的話有兩種方案:
Dep
中的 數(shù)組換為 。subs
Set
- 每個(gè) 對象引入 , 對象中記錄所有的 的 ,下次重新收集依賴的時(shí)候,如果 的 已經(jīng)存在,就不再收集該 了。
Dep
id
Watcher
Dep
id
Dep
id
Watcher
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 對象標(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)變量,全局唯一
在 中,我們引入 來記錄所有的 。Watcher
this.depIds
id
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è)方案:
- 全量式移除,保存 所影響的所有 對象,當(dāng)重新收集 的前,把當(dāng)前 從記錄中的所有 對象中移除。
Watcher
Dep
Watcher
Watcher
Dep
- 增量式移除,重新收集依賴時(shí),用一個(gè)新的變量記錄所有的 對象,之后再和舊的 對象列表比對,如果新的中沒有,舊的中有,就將當(dāng)前 從該 對象中移除。
Dep
Dep
Watcher
Dep
Vue2
中采用的是方案 ,這里也實(shí)現(xiàn)下。2
首先是 類,我們需要提供一個(gè) 方法。Dep
removeSub
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 對象標(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)變量,全局唯一
然后是 類,我們引入 來保存所有的舊 對象,引入 來保存所有的新 對象。Watcher
this.deps
Dep
this.newDeps
Dep
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; // 比對新舊列表,找到舊列表里有,但新列表里沒有,來移除相應(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(); } }
測試
回到開頭的代碼
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.text
updateComponent
data.text
Dep
Watcher
總結(jié)
今天這個(gè)主要就是對響應(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ù)組來存 對象,為什么不直接用 來存 對象呢?id
set
Watcher
Set
id
Dep
Set
Dep
到此這篇關(guān)于Vue2 響應(yīng)式系統(tǒng)之分支切換的文章就介紹到這了,更多相關(guān)Vue2分支切換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解決vue項(xiàng)目nginx部署到非根目錄下刷新空白的問題
今天小編就為大家分享一篇解決vue項(xiàng)目nginx部署到非根目錄下刷新空白的問題,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-09-09vue-element換膚所有主題色和基礎(chǔ)色均可實(shí)現(xiàn)自主配置
這篇文章主要介紹了vue-element換膚所有主題色和基礎(chǔ)色均可實(shí)現(xiàn)自主配置,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-04-04Vue-cli集成axios請求出現(xiàn)CORS跨域問題及解決
這篇文章主要介紹了Vue-cli集成axios請求出現(xiàn)CORS跨域問題及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助,2023-10-10詳解Vue項(xiàng)目引入CreateJS的方法(親測可用)
CreateJS是基于HTML5開發(fā)的一套模塊化的庫和工具。這篇文章主要介紹了Vue項(xiàng)目引入CreateJS的方法(親測),需要的朋友可以參考下2019-05-05

vue實(shí)現(xiàn)搜索并高亮文字的兩種方式總結(jié)

mpvue中配置vuex并持久化到本地Storage圖文教程解析