亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

淺析Proxy如何實現(xiàn)Vue響應(yīng)式

 更新時間:2023年08月04日 09:38:27   作者:前端胖頭魚  
這篇文章主要是來和大家探討一下,Vue的響應(yīng)式系統(tǒng)僅僅是一個Proxy嗎,本文將圍繞此問題探索一下Proxy是如何實現(xiàn)Vue響應(yīng)式的,感興趣的小伙伴可以了解一下

前言

在面試官:Vue3響應(yīng)式系統(tǒng)都不會寫,還敢說精通?中我們實現(xiàn)了一個最基本的響應(yīng)式系統(tǒng)。

它包含以下功能:

  • 借助Proxy將一個對象obj變成響應(yīng)式數(shù)據(jù),攔截其get和set操作。
  • 通過effect注冊副作用函數(shù),并在首次執(zhí)行副作用函數(shù)時完成obj對象的依賴收集(track)。
  • 當(dāng)數(shù)據(jù)發(fā)生變化的時候,第2步注冊的副作用函數(shù)會重新執(zhí)行(trigger)。

回顧源碼

const?bucket?=?new?WeakMap()
//?重新定義bucket數(shù)據(jù)類型為WeakMap
let?activeEffect
const?effect?=?function?(fn)?{
??activeEffect?=?fn
??fn()
}
//?track表示追蹤的意思
function?track?(target,?key)?{
??//?activeEffect無值意味著沒有執(zhí)行effect函數(shù),無法收集依賴,直接return掉
??if?(!activeEffect)?{
????return
??}
??//?每個target在bucket中都是一個Map類型:?key?=>?effects
??let?depsMap?=?bucket.get(target)
??//?第一次攔截,depsMap不存在,先創(chuàng)建聯(lián)系
??if?(!depsMap)?{
????bucket.set(target,?(depsMap?=?new?Map()))
??}
??//?根據(jù)當(dāng)前讀取的key,嘗試讀取key的effects函數(shù)??
??let?deps?=?depsMap.get(key)
??if?(!deps)?{
????//?deps本質(zhì)是個Set結(jié)構(gòu),即一個key可以存在多個effect函數(shù),被多個effect所依賴
????depsMap.set(key,?(deps?=?new?Set()))
??}
??//?將激活的effectFn存進桶中
??deps.add(activeEffect)
}
//?trigger執(zhí)行依賴
function?trigger?(target,?key)?{
??//?讀取depsMap?其結(jié)構(gòu)是?key?=>?effects
??const?depsMap?=?bucket.get(target)
??if?(!depsMap)?{
????return
??}
??//?真正讀取依賴當(dāng)前屬性值key的effects
??const?effects?=?depsMap.get(key)
??//?挨個執(zhí)行即可
??effects?&&?effects.forEach((fn)?=>?fn())
}
//?統(tǒng)一對外暴露響應(yīng)式函數(shù)
function?reactive?(state)?{
??return?new?Proxy(state,?{
????get?(target,?key)?{
??????const?value?=?target[?key?]
??????track(target,?key)
??????//?console.log(`get?${key}:?${value}`)
??????return?value
????},
????set?(target,?key,?newValue)?{
??????//?console.log(`set?${key}:?${newValue}`)
??????//?設(shè)置屬性值
??????target[?key?]?=?newValue
??????trigger(target,?key)
????}
??})
}

測試一下

const?state?=?reactive({
??name:?'fatfish',
??age:?100
})
//?effect1
effect(()?=>?{
??console.log(state.name,?'name')
})
//?effect2
effect(()?=>?{
??console.log(state.age,?'age')
})
state.name?=?'fatfish2'?//?因為name屬性發(fā)生變化了,effect1將會重新執(zhí)行,打印出的name是fatfish2

看起來還不錯,不過他還存在很多缺陷和不足,比如:

  • 分支切換會導(dǎo)致不必要的effect執(zhí)行損耗
  • effect不支持嵌套注冊副作用函數(shù)
  • ...

咱們挨個看看,這都是些啥...

支持分支切換

什么是分支切換?

按照上文的結(jié)論,這段代碼執(zhí)行后會形成這樣的數(shù)據(jù)結(jié)構(gòu)。

state
  |___ok
    |___ effectFn
  |___text
    |___ effectFn

const?state?=?reactive({
??ok:?true,
??text:?'hello?world',
});
effect(()?=>?{
??console.log('渲染執(zhí)行')
??document.querySelector('#app').innerHTML?=?state.ok???state.text?:?'not'
})

當(dāng)我們把ok的值改成false后,頁面將渲染為"not"。意味著后續(xù)無論text如何變化,頁面都永遠(yuǎn)只可能是"not"。

所以當(dāng)我們修改text的值時,副作用函數(shù)重新執(zhí)行是沒有必要的。

const?state?=?reactive({
??ok:?true,
??text:?'hello?world',
});
effect(()?=>?{
??console.log('渲染執(zhí)行')
??document.querySelector('#app').innerHTML?=?state.ok???state.text?:?'not'
})
setTimeout(()?=>?{
??state.ok?=?false?//?此時頁面變成了not
??setTimeout(()?=>?{
????state.text?=?'other'?//?頁面依然是not,但是副作用函數(shù)卻還會執(zhí)行一次
??},?1000)
},?1000)

如何解決?

修改state.text,副作用函數(shù)會執(zhí)行是因為state與其形成的數(shù)據(jù)結(jié)構(gòu)是這樣的。

state
  |___ok
    |___ effectFn
  |___text
    |___ effectFn

如果希望state.text的改動effectFn不再執(zhí)行,我們就要想辦法改變這個結(jié)構(gòu)。

state
  |___ok
    |___ effectFn

此時無論你怎樣修改state.text,effectFn都不會執(zhí)行,因為他們倆之間并沒有形成依賴關(guān)系。

在副作用函數(shù)執(zhí)行前先將其從與該副作用函數(shù)有關(guān)的依賴集合中刪除怎么樣?

比如前面的例子,形成了:

state
  |___ok
    |___ effectFn
  |___text
    |___ effectFn

當(dāng)我們修改state.ok = false時,effectFn將會被執(zhí)行,在執(zhí)行前,我們將effectFn從與之相關(guān)的依賴集合中刪除,最終形成了一個光桿司令。

state

但是不要忘記,effectFn的重新執(zhí)行,又會觸發(fā)一次依賴收集,結(jié)束后,數(shù)據(jù)結(jié)構(gòu)會變成:

state
  |___ok
    |___ effectFn

為了支持這樣的特性,我們需要簡單的改一下effecttrigger函數(shù).

const?effect?=?function?(fn)?{
??const?effectFn?=?()?=>?{
????cleanup(effectFn)
????activeEffect?=?effectFn
????fn()
??}
??//?用來存儲哪些依賴集合包含這個副作用函數(shù)
??effectFn.deps?=?[]
??effectFn()
}
function?cleanup?(effectFn)?{
??for?(let?i?=?0;?i?<?effectFn.deps.length;?i++)?{
????const?deps?=?effectFn.deps[i]
????deps.delete(effectFn)
??}
??effectFn.deps.length?=?0
}

trigger

//?trigger執(zhí)行依賴
function?trigger(target,?key)?{
??//?讀取depsMap?其結(jié)構(gòu)是?key?=>?effects
??const?depsMap?=?bucket.get(target);
??if?(!depsMap)?{
????return;
??}
??//?真正讀取依賴當(dāng)前屬性值key的effects
??const?effects?=?depsMap.get(key);
??//?解決cleanup?執(zhí)行會無限執(zhí)行的問題
??const?effectsToRun?=?new?Set(effects)
??//?挨個執(zhí)行即可
??effectsToRun.forEach((fn)?=>?fn());
}

最后測試一把

const?state?=?reactive({
??ok:?true,
??text:?'hello?world',
});
effect(()?=>?{
??console.log('渲染執(zhí)行')
??document.querySelector('#app').innerHTML?=?state.ok???state.text?:?'not'
})
setTimeout(()?=>?{
??state.ok?=?false?//?頁面渲染為not
??setTimeout(()?=>?{
????state.text?=?'other'?//?頁面依然是not,但是副作用函數(shù)不會再執(zhí)行。
??},?1000)
},?1000)

支持effect嵌套

為什么要支持effect嵌套

先說結(jié)論:因為組件是可以嵌套的,而Vue組件又恰巧是在effect中執(zhí)行的。

來看看Vue中的組件是怎么執(zhí)行的。

const?Foo?=?{
??render?()?{
????return?//?....
??}
}
effect(()?=>?{
??Foo.render()
})

而當(dāng)組件發(fā)生嵌套時,就會存在effect嵌套:

const?Bar?=?{
??render?()?{
????return?//?....
??}
}
const?Foo?=?{
??render?()?{
????return?<Bar?/>?//?...
??}
}

最后會變成這樣:

effect(()?=>?{
??Foo.render()
??effect(()?=>?{
????Bar.render()
??})
})

目前的effect存在什么問題

先來試試看目前它的問題是什么!!!

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})

根據(jù)上一篇文章的結(jié)論,我們認(rèn)為響應(yīng)式數(shù)據(jù)state與副作用函數(shù)應(yīng)該會形成這種數(shù)據(jù)結(jié)構(gòu):

state
  |___foo
    |___ effectFn1
  |___bar
    |___ effectFn2 

所以首次執(zhí)行時會打印出這兩行信息:

當(dāng)我們分別修改foo和bar屬性時會發(fā)生什么?

修改bar

effectFn2會重新執(zhí)行。

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})
setTimeout(()?=>?{
??state.bar?=?false
},?1000)

修改foo

effectFn1會重新執(zhí)行,而effectFn2因為被其嵌套所以會被間接執(zhí)行。 然而現(xiàn)實終歸會告訴我們生活沒那么美好.

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})
setTimeout(()?=>?{
??state.foo?=?false
},?1000)

所以本質(zhì)上形成了這樣的數(shù)據(jù)結(jié)構(gòu),以至于改變foo的值調(diào)用的是effectFn2。

state
  |___foo
    |___ effectFn2
  |___bar
    |___ effectFn2 

問題出在哪里?

當(dāng)effectFn1開始執(zhí)行的時,activeEffect指向的是effectFn1。而effectFn1的執(zhí)行會間接地導(dǎo)致effectFn2的執(zhí)行,此時activeEffect指向的是effectFn2。

const?effect?=?function?(fn)?{
??const?effectFn?=?()?=>?{
????cleanup(effectFn)
????//?問題點~~~
????activeEffect?=?effectFn
????fn()
??}
??//?用來存儲哪些依賴集合包含這個副作用函數(shù)
??effectFn.deps?=?[]
??effectFn()
}

當(dāng)effectFn2執(zhí)行完畢時,因為activeEffect指向的是effectFn2。所以foo自然也就是和effectFn2建立了聯(lián)系,而不是我們期待的effectFn1。

effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})

要解決這個問題也很簡單,我們新維護一個注冊副作用函數(shù)的棧,讓activeEffect指向的是永遠(yuǎn)是棧頂?shù)母弊饔煤瘮?shù)。用上面例子來模擬一下這個過程。

//?第1步:effectFn1執(zhí)行入棧
//?effectFn1?←?activeEffect
//?第2步:effectFn2執(zhí)行入棧
/*
??此時棧變成了
??effectFn2?←activeEffect
??effectFn1
*/
//?第3步:effectFn2執(zhí)行完畢,將effectFn2出棧處理
//?effectFn1?←activeEffect
//?第4步:effectFn1執(zhí)行完畢,將effectFn1出棧處理
//?此時棧已是空的

所以我們很容易對effect做出以下改造:

const?bucket?=?new?WeakMap();
const?effectStack?=?[]
//?重新定義bucket數(shù)據(jù)類型為WeakMap
let?activeEffect;
const?effect?=?function?(fn)?{
??const?effectFn?=?()?=>?{
????cleanup(effectFn)
????activeEffect?=?effectFn
????//?入棧
????effectStack.push(effectFn)
????fn()
????//?出棧
????effectStack.pop()
????activeEffect?=?effectStack[?effectStack.length?-?1?]
??}
??//?用來存儲哪些依賴集合包含這個副作用函數(shù)
??effectFn.deps?=?[]
??effectFn()
??console.log(effectStack.length,?'---')
??//?非常重要
??//?activeEffect?=?null
};

再測試一下上面的例子,一秒鐘后成功的打印了effectFn1和effectFn2

const?state?=?reactive({
??foo:?true,
??bar:?true
})
effect(function?effectFn1?()?{
??console.log('effectFn1')
??effect(function?effectFn2?()?{
????console.log('effectFn2')
????console.log('Bar',?state.bar)
??})
??console.log('Foo',?state.foo)
})
setTimeout(()?=>?{
??state.foo?=?false
},?1000)

到此這篇關(guān)于淺析Proxy如何實現(xiàn)Vue響應(yīng)式的文章就介紹到這了,更多相關(guān)Vue Proxy響應(yīng)式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • vue實現(xiàn)短信驗證碼登錄功能(流程詳解)

    vue實現(xiàn)短信驗證碼登錄功能(流程詳解)

    無論是移動端還是pc端登錄或者注冊界面都會見到手機驗證碼登錄這個功能,輸入手機號,得到驗證碼,這篇文章主要介紹了基于vue實現(xiàn)短信驗證碼登錄功能,需要的朋友可以參考下
    2019-12-12
  • Vue 數(shù)組和對象更新,但是頁面沒有刷新的解決方式

    Vue 數(shù)組和對象更新,但是頁面沒有刷新的解決方式

    今天小編就為大家分享一篇Vue 數(shù)組和對象更新,但是頁面沒有刷新的解決方式,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-11-11
  • Vue.js雙向綁定實現(xiàn)原理詳解

    Vue.js雙向綁定實現(xiàn)原理詳解

    這篇文章主要為大家詳細(xì)介紹了Vue.js雙向綁定實現(xiàn)原理,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • vue對storejs獲取的數(shù)據(jù)進行處理時遇到的幾種問題小結(jié)

    vue對storejs獲取的數(shù)據(jù)進行處理時遇到的幾種問題小結(jié)

    這篇文章主要介紹了vue對storejs獲取的數(shù)據(jù)進行處理時遇到的幾種問題小結(jié),需要的朋友可以參考下
    2018-03-03
  • springboot?vue接口測試前端動態(tài)增刪表單功能實現(xiàn)

    springboot?vue接口測試前端動態(tài)增刪表單功能實現(xiàn)

    這篇文章主要為大家介紹了springboot?vue接口測試前端動態(tài)增刪表單功能實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-05-05
  • Vue中使一個div鋪滿全屏的實現(xiàn)

    Vue中使一個div鋪滿全屏的實現(xiàn)

    最近在項目開發(fā)中,就遇到了這個問題,Vue中如何使一個div鋪滿全屏,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-07-07
  • Vue 項目部署到服務(wù)器的問題解決方法

    Vue 項目部署到服務(wù)器的問題解決方法

    本篇文章主要介紹了Vue 項目部署到服務(wù)器的問題解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-12-12
  • Vue數(shù)組更新及過濾排序功能

    Vue數(shù)組更新及過濾排序功能

    Vue為了增加列表渲染的功能,增加了一組觀察數(shù)組的方法,而且可以顯示一個數(shù)組的過濾或排序的副本。本文將詳細(xì)介紹Vue數(shù)組更新及過濾排序
    2017-08-08
  • element UI 中的 el-tree 實現(xiàn) checkbox 單選框及 bus 傳遞參數(shù)功能

    element UI 中的 el-tree 實現(xiàn) checkbox&n

    在日常項目開發(fā)中,會經(jīng)常遇到,樹形結(jié)構(gòu)的查詢方式,為了快速方便開發(fā),常常會使用到快捷的ui組件去快速搭樹形結(jié)構(gòu),這里我用的是 element ui 中的 el-tree,對element UI 中的 el-tree 實現(xiàn) checkbox 單選框及 bus 傳遞參數(shù)的方法感興趣的朋友跟隨小編一起看看吧
    2022-09-09
  • 解決elementUI中el-tree樹形結(jié)構(gòu)中節(jié)點過濾的問題

    解決elementUI中el-tree樹形結(jié)構(gòu)中節(jié)點過濾的問題

    這篇文章主要介紹了解決elementUI中el-tree樹形結(jié)構(gòu)中節(jié)點過濾的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04

最新評論