在 Vue3 的響應式系統中,雙向鏈表是一個非常重要的數據結構。相比 Vue2 使用陣列來存放依賴,Vue3 選擇鏈表的原因在於效率更高,尤其是在頻繁收集和清理依賴時,鏈表可以顯著優化性能。本文將通過講解和示例代碼,幫助你理解這一點。
在響應式依賴收集過程中,Vue 需要完成兩件事:
effect
)。如果依賴集合使用陣列:
使用雙向鏈表:
每個鏈表節點包含:
effect
:保存副作用函數prev
:指向前一個節點next
:指向後一個節點通過 prev
和 next
,節點可以快速被插入或刪除。
示例:
A <-> B <-> C
刪除 B:
A.next = C
C.prev = A
下面的示例展示了如何通過雙向鏈表來管理依賴的添加、刪除和觸發。
// 鏈表節點:存儲副作用函數和前後指針
class EffectNode {
constructor(effect) {
this.effect = effect; // 要執行的副作用函數
this.prev = null; // 前一個節點
this.next = null; // 後一個節點
this.dep = null; // 當前節點所屬的依賴集合
}
}
// 依賴集合(Dep):使用雙向鏈表存儲多個 effect
class Dep {
constructor() {
this.head = null; // 鏈表頭
this.tail = null; // 鏈表尾
}
// 添加依賴:O(1),插入到鏈表尾部
add(effect) {
const node = new EffectNode(effect);
node.dep = this;
if (!this.head) {
// 如果鏈表為空,頭尾都指向當前節點
this.head = this.tail = node;
} else {
// 否則插入到尾部
this.tail.next = node;
node.prev = this.tail;
this.tail = node;
}
return node; // 返回節點,方便以後刪除
}
// 刪除依賴:O(1),只需修改相鄰節點指針
remove(node) {
if (node.prev) node.prev.next = node.next;
if (node.next) node.next.prev = node.prev;
if (node === this.head) this.head = node.next;
if (node === this.tail) this.tail = node.prev;
// 清理引用,幫助垃圾回收
node.prev = node.next = node.dep = null;
}
// 觸發依賴:遍歷鏈表,執行所有副作用函數
trigger() {
let cur = this.head;
while (cur) {
cur.effect();
cur = cur.next;
}
}
}
// 模擬響應式數據
const state = { count: 0 };
// 每個屬性都可能有一個依賴集合,這裡用一個 dep 演示
const dep = new Dep();
// 註冊副作用函數,並收集依賴
function effect(fn) {
const runner = () => fn();
const node = dep.add(runner);
runner(); // 立即執行一次
return node; // 返回節點,方便後續刪除
}
// 使用:收集依賴
const node = effect(() => {
console.log("副作用執行: count =", state.count);
});
// 修改數據時觸發依賴
state.count++;
dep.trigger(); // 輸出: 副作用執行: count = 1
// 刪除依賴,再次觸發時不會執行
dep.remove(node);
state.count++;
dep.trigger(); // 無輸出
副作用執行: count = 0
副作用執行: count = 1
在移除依賴後,再次修改數據時不會有輸出,說明鏈表刪除操作成功。
這就是 Vue3 響應式系統比 Vue2 更快的一個重要原因。
本文部分內容借助 AI 輔助生成,並由作者整理審核。