您好,登錄后才能下訂單哦!
本篇內容介紹了“Vue2響應式系統之分支切換怎么實現”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
我們考慮一下下邊的代碼會輸出什么。
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 執行一次函數,輸出 hello, world data.ok = false; // updateComponent 執行一次函數,輸出 not data.text = "hello, liang"; // updateComponent 會執行嗎?
我們來一步一步理清:
攔截了 中 和 的 ,并且各自初始化了一個 實例,用來保存依賴它們的 對象。data
text
ok
get、set
Dep
Watcher
這一步會執行 函數,執行過程中用到的所有對象屬性,會將 收集到相應對象屬性中的 中。updateComponent
Watcher
Dep
當然這里的 其實是同一個,所以用了指向的箭頭。Watcher
這一步會觸發 ,從而執行 中所有的 ,此時就會執行一次 。set
Dep
Watcher
updateComponent
執行 就會重新讀取 中的屬性,觸發 ,然后繼續收集 。updateComponent
data
get
Watcher
重新執行 函數 的時候:updateComponent
const updateComponent = () => { console.log("收到", data.ok ? data.text : "not"); };
因為 的值變為 ,所以就不會觸發 的 , 的 就不會變化了。data.ok
false
data.text
get
text
Dep
而 會繼續執行,觸發 收集 ,但由于我們 中使用的是數組,此時收集到的兩個 其實是同一個,這里是有問題,會導致 重復執行,一會兒我們來解決下。data.ok
get
Watcher
Dep
Wacher
updateComponent
執行這句的時候,會觸發 的 ,所以會執行一次 。但從代碼來看 函數中由于 為 , 對輸出沒有任何影響,這次執行其實是沒有必要的。text
set
updateComponent
updateComponent
data.ok
false
data.text
之所以執行了,是因為第一次執行 讀取了 從而收集了 ,第二次執行 的時候, 雖然沒有讀到,但之前的 也沒有清除掉,所以這一次改變 的時候 依舊會執行。updateComponent
data.text
Watcher
updateComponent
data.text
Watcher
data.text
updateComponent
所以我們需要的就是當重新執行 的時候,如果 已經不依賴于某個 了,我們需要將當前 從該 中移除掉。updateComponent
Watcher
Dep
Watcher
Dep
總結下來我們需要做兩件事情。
去重, 中不要重復收集 。Dep
Watcher
重置,如果該屬性對 中的 已經沒有影響了(換句話就是, 中的 已經不會讀取到該屬性了 ),就將該 從該屬性的 中刪除。Dep
Wacher
Watcher
updateComponent
Watcher
Dep
去重的話有兩種方案:
Dep
中的 數組換為 。subs
Set
每個 對象引入 , 對象中記錄所有的 的 ,下次重新收集依賴的時候,如果 的 已經存在,就不再收集該 了。Dep
id
Watcher
Dep
id
Dep
id
Watcher
Vue2
源碼中采用的是方案 這里我們實現下:2
Dep
類的話只需要引入 即可。id
/*************改動***************************/ let uid = 0; /****************************************/ export default class Dep { static target; //當前在執行的函數 subs; // 依賴的函數 id; // Dep 對象標識 constructor() { /**************改動**************************/ this.id = uid++; /****************************************/ this.subs = []; // 保存所有需要執行的函數 } addSub(sub) { this.subs.push(sub); } depend() { if (Dep.target) { // 委托給 Dep.target 去調用 addSub Dep.target.addDep(this); } } notify() { for (let i = 0, l = this.subs.length; i < l; i++) { this.subs[i].update(); } } } Dep.target = null; // 靜態變量,全局唯一
在 中,我們引入 來記錄所有的 。Watcher
this.depIds
id
import Dep from "./dep"; export default class Watcher { constructor(Fn) { this.getter = Fn; /*************改動***************************/ this.depIds = new Set(); // 擁有 has 函數可以判斷是否存在某個 id /****************************************/ this.get(); } /** * Evaluate the getter, and re-collect dependencies. */ get() { Dep.target = this; // 保存包裝了當前正在執行的函數的 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; 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(); } }
同樣是兩個方案:
全量式移除,保存 所影響的所有 對象,當重新收集 的前,把當前 從記錄中的所有 對象中移除。Watcher
Dep
Watcher
Watcher
Dep
增量式移除,重新收集依賴時,用一個新的變量記錄所有的 對象,之后再和舊的 對象列表比對,如果新的中沒有,舊的中有,就將當前 從該 對象中移除。Dep
Dep
Watcher
Dep
Vue2
中采用的是方案 ,這里也實現下。2
首先是 類,我們需要提供一個 方法。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; //當前在執行的函數 subs; // 依賴的函數 id; // Dep 對象標識 constructor() { this.id = uid++; this.subs = []; // 保存所有需要執行的函數 } addSub(sub) { this.subs.push(sub); } /*************新增************************/ removeSub(sub) { remove(this.subs, sub); } /****************************************/ depend() { if (Dep.target) { // 委托給 Dep.target 去調用 addSub Dep.target.addDep(this); } } notify() { for (let i = 0, l = this.subs.length; i < l; i++) { this.subs[i].update(); } } } Dep.target = null; // 靜態變量,全局唯一
然后是 類,我們引入 來保存所有的舊 對象,引入 來保存所有的新 對象。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 函數可以判斷是否存在某個 id /*************新增************************/ this.deps = []; this.newDeps = []; // 記錄新一次的依賴 this.newDepIds = new Set(); /****************************************/ this.get(); } /** * Evaluate the getter, and re-collect dependencies. */ get() { Dep.target = this; // 保存包裝了當前正在執行的函數的 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; /*************新增************************/ // 新的依賴已經存在的話,同樣不需要繼續保存 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; // 比對新舊列表,找到舊列表里有,但新列表里沒有,來移除相應 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 執行一次函數,輸出 hello, world data.ok = false; // updateComponent 執行一次函數,輸出 not data.text = "hello, liang"; // updateComponent 會執行嗎?
此時 修改的話就不會再執行 了,因為第二次執行的時候,我們把 中 里的 清除了。data.text
updateComponent
data.text
Dep
Watcher
“Vue2響應式系統之分支切換怎么實現”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。