回應式原理
原始碼目錄:https://github.com/vuejs/vue-next/tree/master/packages/reactivity
模塊
ref:
reactive:
computed:
effect:
operations:提供TrackOpTypes和TriggerOpTypes兩個列舉型別,供其他模塊使用
剖析
Vue2回應式原理
什么是回應式資料?即A依賴于B資料,當B值發生變化時,通知A,很顯然,這里應該使用觀察者模式
在vue2中的回應式原理:剖析Vue原理&實作雙向系結MVVM
上面的文章將整個Vue的大致實作都分析了,就回應式這塊來說,大概的邏輯是這幾個模塊Observer,Watcher,Dep,
Observer負責通過defineProperty劫持資料Data,每個被劫持的Data都各自在閉包中維護一個Dep的實體,用于收集依賴著它的Watcher【即觀察者】(都實作了一個update方法),被收集的Watcher存入Dep實體的subs陣列中,如果Data是物件,則遞回搜集,
Dep維護一個公共的Target屬性,在觸發劫持前,將Target設定為當前Watcher, 然后觸發getter將Target(Watcher)收集到subs中,然后再將Target置為null
Data資料變更的時候觸發setter,然后從Data維護的Dep實體的subs陣列中將Watcher取出來一一執行其update方法,如果變更的值是物件,再劫持之,
用一個最簡單的偽代碼來說明(省略掉了對值是復雜資料的處理,原理是一樣的)
// Vue2回應式原理的基本使用(偽代碼)
data = https://www.cnblogs.com/chuaWeb/p/{ age: 10 };
new Observer(data) // 資料劫持,黑色箭頭
new Wachter(target,'age', function update() { ... }) // 添加觀察者,綠色箭頭
data.age = 20 // 被觀察者變更,通知觀察者, 紅色箭頭
對應的資料流程如下

就上面的程序,實際上還是有比較大的問題
1.如果Watcher使用的Data是物件型別,那么Data中所有的子屬性都需要遞回將Watcher收集,這是個資源浪費,
2.資料劫持和依賴收集是強耦合關系
3.對陣列的劫持也沒有做好,部分操作不是回應式的,
effect.ts
為了解決vue2的問題,依賴收集(即添加觀察者/通知觀察者)模塊單獨出來,就是現在的effect
用來生成/處理/追蹤reactiveEffect資料,主要是收集資料依賴(觀察者),通知收集的依賴(觀察者),
提供了三個函式主要函式:effect/track/trigger,
effect是將傳入的函式轉化為reactiveEffect格式的函式
track主要功能是將reactiveEffect添加為target[key]的觀察者
trigger主要功能是通知target[key]的觀察者(將觀察者佇列函式一一取出來執行)
effect(fn, options):ReactiveEffect
回傳一個effect資料:reactiveEffect函式,
執行reactiveEffect即可將資料加入可追蹤佇列effectStack,并將當前資料設定為activeEffect,并執行fn,fn執行完畢之后恢復activeEffect,
【注意】:必須要在fn函式中執行track才能將reactiveEffect添加為target[key]的觀察者,因為track內部只會處理當前的activeEffect,activeEffect沒有值則直接回傳
track(target, type, key)
將activeEffect添加為target[key]的觀察者,如果activeEffect無值,則直接回傳,target[key]資料被快取到targetMap中以{target-> key-> dep}格式存盤,優化記憶體開銷,
當前activeEffect(在呼叫reactiveEffect函式時會將reactiveEffect設定為activeEffect)添加為target[key]的觀察者,被添加到target[key]的觀察者佇列dep中【dep.add(activeEffect)】
當前target[key]的觀察者佇列dep也會被activeEffect收集【activeEffect.deps.push(dep)】
trigger(target, type, key, newValue, oldValue, oldTarget)
通知target[key]的觀察者,即target-> key-> dep中存放的資料,全部一一取出來執行
如果觀察者有提供scheduler則執行scheduler函式,否則執行觀察者(函式型別)本身
流程是:
首先要將某個函式fn包裹一層為reactiveEffect函式,
當執行reactiveEffect函式時內部會將當前reactiveEffect函式標記為activeEffect,然后執行fn,
fn內部可以呼叫track,將activeEffect添加為target[key]的觀察者,加入佇列dep中,當然activeEffect也收集了target[key]的觀察者佇列dep,
這時,如果修改target[key]的值,然后呼叫trigger,觸發通知target[key]的觀察者,trigger中會將對應的觀察者佇列中的觀察者一一取出執行,
import { effect, track, trigger } from 'vue'
let target = {
age: 10
}
const fn = () => {
// 將fn對應的reactiveEffect函式添加到target.age的觀察者佇列
track(target, 'get', 'age')
// 觸發target.age的trigger【通知觀察者】, 也會執行該函式
}
// 將fn函式包裹一層為reactiveEffect函式
const myEffect = effect(fn, { lazy: true })
// myEffect每次執行都會將自己設定為activeEffect,并執行fn函式
// fn內部會將對應的reactiveEffect函式添加到target.age的觀察者佇列
myEffect()
// 設定新值并手動通知target.age的所有觀察者
target.age = 20
// 通知target.age的觀察者
trigger(target, 'set', 'age')
結合流程說明看這段代碼,資料流圖

理論上來說,將reactiveEffect添加為target[key]的觀察者不一定要在fn中進行,但不這樣,用戶需要手動為target[key]指定觀察者,形如
activeEffect = reactiveEffect
track(target, 'get', 'age') // 內部會將activeEffect添加為target.age的觀察者
activeEffect = null
為了簡化處理,reactiveEffect內部處理為
// reactiveEffect 內部
try {
effectStack.push(effect)
// 當前effect設定為activeEffect
// 第一次track被呼叫時,該effect會被加入effectStack
activeEffect = effect
// 執行fn的程序中會對activeEffect做處理
return fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
在fn執行之前已經將reactiveEffect設定為activeEffect,并且fn執行完畢之后會恢復activeEffect,
這樣fn中只需要呼叫一下track,就將fn對應的reactiveEffect添加為target.age的觀察者了,代碼如下
// fn
const fn = () => {
...
track(target, 'get', 'age')
return get.age
}
我們將最開始的那個例子改造成一個更加真實的的例子
import { effect, track, trigger } from 'vue'
let target = {
_age: 10,
set age(val) {
this._age = val
trigger(this, 'set', 'age')
}
}
const watcher = () => {
console.log('target.age有更改,則通知我')
}
const fn = () => {
if(!target._isTracked){
target._isTracked = true
track(target, 'get', 'age')
console.log('添加fn的reactiveEffect函式添加到target.age的觀察者佇列')
}else{
watcher()
}
}
fn._isTracked = false
const myEffect = effect(fn, { lazy: true })
myEffect() //列印: '添加fn的reactiveEffect函式添加到target.age的觀察者佇列'
target.age = 20 //列印: '觸發target.age的trigger【通知觀察者】, 進入此處'
歡迎造訪本人剖析vue3的github倉庫
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/3059.html
標籤:JavaScript
下一篇:XSS基礎學習
