Vue2和Vue3響應式原理對比剖析

響應式的過程

1.知道收集視圖依賴了哪些數據
2.感知被依賴數據的變化
3.數據變化時,自動“通知”需要更新的視圖部分,并進行更新

響應式原理實現邏輯

  • 依賴收集
  • 數據劫持、數據代理
  • 發布/訂閱模式

Vue2響應式原理簡化

1.對象響應化:遞歸遍歷每個key,使用Object.defineproperty方法定義getter、setter
2.數組響應化:采用函數攔截方式,覆蓋數組原型方法,額外增加通知邏輯

  • 響應式處理

//響應式處理
function observe(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return  
    }  
    // 增加數組類型判斷,若是數組則覆蓋其原型  
    if (Array.isArray(obj)) {
        Object.setPrototypeOf(obj, arrayProto)  
    } else {
        //對象遍歷處理
        const keys = Object.keys(obj)
        for (let i = 0; i < keys.length; i++) {
                        // defineReactive方法在vue源碼中,主要是利用 Object.defineProperty 函數作用于對象屬性的值,進行取值和賦值操作時的攔截和處理
            const key = keys[i]defineReactive(obj, key, obj[key])   
        }  
    } 
}
  • 數組處理

const originalProto = Array.prototype
//拷貝一份數組原型方法
const arrayProto = Object.create(originalProto);
//這七個方法會讓數組的長度或順序發生變化,需要單獨處理
['push', 'pop', 'shift','unshift','splice','reverse','sort'].forEach(
method => {
    //方法重寫
    arrayProto[method] = function() {
        originalProto[method].apply(this, arguments)
        //處理項進行響應式化
        observe(inserted)
        //派發更新
        dep.notify() 
    }  
})
  • 對象處理

function defineReactive(obj, key, val) {  
    observe(val) // 解決嵌套對象問題  
    Object.defineProperty(obj, key, {
        get() {
            //依賴收集
            dep.depend()
            return val   
        },
        set(newVal) {
            if (newVal !== val) {
                observe(newVal) // 新值是對象的情況
                val = newVal 
                //派發更新
                dep.notify() 
            }    
        } 
    })
}
image.png

Vue2響應式原理弊端

  1. 響應化過程需要遞歸遍歷消耗較大
    2.新加或刪除屬性無法監聽數組響應化需要額外實現
    3.Map、Set、Class等無法響應式修改
    4.語法有限制

Vue3響應式原理簡化

vue3中使用ES6的Proxy特性來實現響應式
可以一次性友好的解決對象和數組

設計原理

effect:將回調函數保存起來備用,立即執行一次回調函數觸發它里面一些響應數據的getter
track(依賴收集):getter中調用track,把前面存儲的回調函數和當前target,key之間建立映射關系
trigger(派發更新):setter中調用trigger,把target,key對應的響應函數都執行一遍

const isObject = val => typeof val === 'object' && val !== null

  • 緩存已處理的對象,避免重復代理

  • WeakMap 對象是一組鍵/值對的集合,其中鍵是弱引用的,必須是對象,而值可以是任意的。
const toProxy = new WeakMap() //形如obj:observed
const toRaw = new WeakMap() //形如observed:obj
function reactive(obj){
    if(!isObject(obj)){
        return obj
    }
    //查找緩存,避免重復代理
    if(toProxy.has(obj)){
        return toProxy.get(obj)
    }
    if(toRaw.has(obj)){
        return obj
    }
    /*
    Proxy兩個參數
    target:要使用 Proxy 包裝的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)。
    handler:一個通常以函數作為屬性的對象,各屬性中的函數分別定義了在執行各種操作時代理 p 的行為。 
    */
    const observed = new Proxy(obj,{
        //Reflect 是一個內置的對象,它提供攔截 JavaScript 操作的方法。這些方法與proxy handlers的方法相同
        get(target,key,receiver){
            const res = Reflect.get(target,key,receiver)
            //依賴收集
            track(target,key)
            //遞歸處理嵌套對象
            return isObject(res)?reactive(res):res
        },
        set(target,key,value,receiver){
            const res = Reflect.set(target,key,value,receiver)
            //觸發響應函數
            trigger(target,key)
            return res
        },
        deleteProperty(target,key){
            const res = Reflect.deleteProperty(target,key)
            return res
        }
    })
    //緩存代理結果
    toProxy.set(obj,observed)
    toRaw.set(observed,obj)
    return observed
}

//保存當前活動響應函數作為getter和effect之間的橋梁
const effectStack = []
//設置響應函數,創建effect函數,執行fn并將其入棧
function effect(fn){
    const rxEffect = function(){
        //捕獲可能的異常
        try{
            //入棧,用于后續依賴收集
            effectStack.push(rxEffect)
            //運行fn,觸發依賴收集
            return fn()
        }finally{
            //執行結束,出棧
            effectStack.pop()
        }
    }
    //默認執行一次響應函數
    rxEffect()
    //返回響應函數
    return rxEffect
}

//映射關系表
//{target:{key:[fn1,fn2]}}
let targetMap = new WeakMap()
function track(target,key){
    //從棧中取出響應式函數
    const effect = effectStack[effectStack.length - 1]
    if(effect){
        let depsMap = targetMap.get(target)
        if(!depsMap){
            depsMap = new Map()
            targetMap.set(target,depsMap)
        }
        //獲取key對應的響應函數集
        let deps = depsMap.get(key)
        if(!deps){
            deps = new Set()
            depsMap.set(key,deps)
        }
        //將響應函數加入到對應集合
        deps.add(effect)
    }
}

//觸發target,key對應響應函數
function trigger(target,key){
    //獲取依賴表
    const depsMap = targetMap.get(target)
    if(depsMap){
        //獲取響應函數集合
        const deps = depsMap.get(key)
        console.log(deps)
        if(deps){
            //執行所有響應函數
            deps.forEach(effect=>{
                effect()
            })
        }
    }
}


//使用
//設置響應函數
const state = reactive({
    foo:"aaa"
})
effect(()=>{
    console.log(state.foo)//aaa   bbb
})
state.foo
state.foo = "bbb"
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容