<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
我們考慮一下下邊的程式碼會輸出什麼。
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
今天這個主要就是對響應式系統的一點優化,避免不必要的重新執行。所做的事情就是重新呼叫函數的時候,把已經沒有關聯的 去除。Watcher
不知道看到這裡大家有沒有一個疑問,我是一直沒想到說服我的點,歡迎一起交流:
在解決去重問題上,我們是引入了 ,但如果直接用 其實就可以。在 類中是用 來存 ,用陣列來存 物件,為什麼不直接用 來存 物件呢?id
set
Watcher
Set
id
Dep
Set
Dep
到此這篇關於Vue2 響應式系統之分支切換的文章就介紹到這了,更多相關Vue2分支切換內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45