vue源碼系列05_發佈訂閱模式
所謂依賴收集,就是在每個數據渲染更新的時候,給每個數據添加一個watcher監聽類,當該數據發生變化時,用一個dep隊列來實現收集這些watcher,最後逐一觸發watcher中的update進行渲染
這是網友的一張圖
概要:
- 在數據初始化的時候,會給每個屬性配置一個dep類監視並綁定給同一個watcher (該watcher是全局watcher)
- 當第一次頁面渲染的時候,全局watcher會默認執行update方法進行頁面更新
- dep內部有一個棧專門保存watcher,當dep負責的那個屬性數據發生改變時,會依次觸發棧內所有watcher的update方法進行頁面渲染。(發佈)
- 當然watcher(視圖)可以包含多個dep(訂閱),當它所負責dep中有一個數據發生改變了,那個dep會觸發 notify() 方法使得該 watcher 觸發 update() 進行頁面渲染
可以簡單地理解爲:
- 一個視圖(watcher)可以包含多個dep(數據),當它所包含的dep(數據)發生改變時(數據劫持),該視圖就會自動觸發更新,而其他視圖(watcher)如果沒有包含發生數據變化的那個dep,就不會發生視圖更新從而實現局部更新
- 一個數據(dep)也可以同時運用在多個視圖中(watcher),當該dep所監視的數據發生改變時,它所保存的視圖(watcher)將會按順序發生視圖更新,並且不影響其他watcher(發佈)
以下爲依賴收集的流程
dep.js
主要的操作:
- 給不同的dep賦一個id
- 創建sub數組(實際上是隊列)
- 創建stack棧,創建保存watcher與取出watcher的方法,並且讓 Dep.target 得到當前 watcher (後面有用到)
主要的幾個方法
- addSub() 訂閱
向 sub 數組添加一個watcher - notify() 發佈
遍歷 sub 數組,讓每個watcher執行 update() 方法 - depend() 存watcher
調用watcher.addDep(this),讓watcher記錄Dep
let id = 0
class Dep {
constructor(){
this.id = id++
this.subs = []
}
addSub(watcher){ //訂閱
this.subs.push(watcher)
}
notify(){ //發佈
this.subs.forEach(watcher =>{
watcher.update()
})
}
depend(){
if (Dep.target){
Dep.target.addDep(this)
}
}
}
// 保存當前watcher
let stack = []
export function pushTarget(watcher) {
Dep.target = watcher
stack.push(watcher)
}
export function popTarget() {
stack.pop()
Dep.target = stack[stack.length - 1]
}
export default Dep
watcher.js
- 在 get() 方法中,在更新方法之前壓入棧,更新之後退出棧
get() {
pushTarget(this)
let value = this.getter()
popTarget()
return value // 返回老值
}
- 添加update更新方法
- 給每個watcher添加 dep數組與depId的Set集合
- 添加 addDep() 方法 去重
addDep(dep) {
let id = dep.id
// 當該watcher沒有相同的 dep
if (!this.depsId.has(id)) {
this.depsId.add(id)
this.deps.push(dep) // 記錄當前dep
dep.addSub(this) // 訂閱
}
}
- 實現異步更新,這一步是爲了防止同一個watcher連續多次刷新,通過這一步我們可以實現只運行最後一步,提高性能(批量更新)
let has = {}
let queue = [];
function flusqueue() {
queue.forEach(watcher => watcher.run())
has = {};
queue = []
}
function queueWatcher(watcher) {
let id = watcher.id
if (has[id] == null) {
has[id] = true
queue.push(watcher)
}
nextTick(flusqueue)
}
// 異步執行
let callbacks = [];
function flushCallbacks() {
callbacks.forEach(cb => cb())
}
function nextTick(flusqueue) {
callbacks.push(flusqueue)
let asyncFn = () => {
flushCallbacks()
}
if (Promise) {
Promise.resolve().then(asyncFn)
}
setTimeout(asyncFn, 0)
}
defineReactive()
在每個數據添加響應式的時候,我們給它們添加一個dep,當屬性設置的時候,實現更新 dep.notify()
export function defineReactive(data, key, value) {
let childOb = observe(value) // 遞歸,對data中的對象進行響應式操作,遞歸實現深度檢測
let dep = new Dep()
Object.defineProperty(data, key, {
get() { // 獲取值的時候做一些操作
if(Dep.target){
// wacher 裏面記錄dep 也在dep裏面記錄watcher
dep.depend()
// dep.addSub(watcher)
if(childOb){
childOb.dep.depend() // 數組收集當前渲染的watcher
dependArray(value) // 收集兒子的依賴
}
}
return value;
},
set(newValue) { // 也可以做一些操作
console.log("設置屬性",newValue)
if (newValue == value) return;
observe(newValue); //繼續劫持用戶設置的值,因爲用戶設置的值有可能是一個對象
value = newValue;
// 當屬性設置的時候,實現更新
dep.notify()
}
})
}