vue3為什么要用proxy替代defineProperty
在這之前,我們得先了解下vue的核心理念mutable
不管是vue2還是vue3,在實現(xiàn)的過程中,核心概念一直保持穩(wěn)定,以可變數(shù)據(jù)源為核心的理念,來實現(xiàn)整個UI變動更新
用最簡單的講法就是:初始化數(shù)據(jù)生成了頁面,直接修改源數(shù)據(jù)觸發(fā)更新,頁面重新渲染
關注vue的人都知道,vue3里面使用了proxy替換了defineProperty,
在使用vue2的時候,我們經(jīng)常會碰到一個問題,添加新的對象屬性obj.a = 1會無法被vue2劫持,必須使用vue2提供的$set方法來進行更新
這個的原因想必大家也都清楚,因為defineProperty只能對當前對象的其中一個屬性進行劫持
const a = { b: 1, }; Object.defineProperty(a, 'b', { set: function() {}, get: function() {}, });
當我們給a對象新增一個屬性的時候,當前新增的屬性并沒有被defineProperty劫持,雖然在對應的對象上依舊成功的生成了一個新的屬性,但是我們知道,vue2是通過defineProperty的setter與getter進行數(shù)據(jù)劫持的,既然新增的數(shù)據(jù)并沒有被劫持,所以無論怎么更新,頁面依舊不會重新渲染
而在vue3中,使用proxy來進行數(shù)據(jù)代理就完全沒有這個顧慮了
const p = new Proxy({ a: 1, b: 2, }, { get: function(obj, value) { console.log('get', obj, value); return Reflect.get(obj, value); }, set: function(obj, prop, value) { console.log('set', obj, prop, value); return Reflect.set(obj, prop, value); }, })
proxy對于數(shù)據(jù)的代理,是能夠響應新增的屬性,當新增一個屬性的時候,可以響應到get中,對當前對象進行代理
vue3是如何通過proxy代理的
首先可以看下vue3新增的幾個主要apiref, reactive, effect,computed
ref和reactive const normal = ref(0); const state = reactive({ a: 1, b: 2, })
vue3中對vue2的兼容處理也是使用了reactive,即instance.data = reactive(data),將整個data屬性使用reactive進行代理
我們知道,vue2中的data就是使用Object.definePerproty進行數(shù)據(jù)劫持的, 那么在reactive中,他是如何使用proxy進行數(shù)據(jù)代理的,來兼容老的書寫方式與新的compositionApi
ps: 由于在reactive里面也只是通過proxy對傳入的數(shù)據(jù)校驗和代理,最主要的還是set和get,所以我們還是直接上壘吧,畢竟心急吃得了熱豆腐
get
可以分析一下,vue2也好,vue3也罷,針對于數(shù)據(jù)的獲取所做的事情主要內(nèi)容不會有什么區(qū)別
- 獲取當前需要的key的數(shù)據(jù)
- 依賴采集
但是,針對于vue3使用proxy的特性,在這邊額外做了一部分的兼容
- 如果獲取的數(shù)據(jù)是一個對象,則會對對象再使用reactive進行一次數(shù)據(jù)的代理
- 如果是shallow類型的數(shù)據(jù)代理, 則直接返回當前獲取到的數(shù)據(jù)
effect依賴采集
vue除去正常的data的數(shù)據(jù)代理以外,還有對應的computed和watch,而在vue3中直接使用了watchEffect和computed方法能夠直接生成對應的內(nèi)容
他們的數(shù)據(jù)更新和依賴處理都是依托于當前data數(shù)據(jù)上的get進行依賴的收集
掐頭去尾的來看最核心的代碼
// targetMap當前所有代理的數(shù)據(jù)的一個Map集合 // depsMap當前代理的數(shù)據(jù)的每一個Key所對應的Map集合 // dep當前代理的數(shù)據(jù)中的key的對應依賴 // activeEffect當前由effect或者computed生成的數(shù)據(jù) let depsMap = targetMap.get(target) if (!depsMap) { targetMap.set(target, (depsMap = new Map())) } let dep = depsMap.get(key) if (!dep) { depsMap.set(key, (dep = new Set())) } if (!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) }
單純從這段代碼出發(fā)去解讀的話,可能會有一定的困難,換個角度,從vue3的整體使用情況出發(fā),返回來解讀這段代碼
setup() { const b = reactive({ c: 1, b: 2, }); // effect是vue中的reactivity包直接返回出來的方法 const a = effect(() => { return b.c; }) }
首先,在effect中使用了前面通過reactive定義的b,從表面現(xiàn)象出發(fā)的話,我們能知道,當b.c發(fā)生變化的時候,a也會同步發(fā)生變化
這個變化的原因就是上述源碼中的activeEffect,當創(chuàng)建的effect被調(diào)用的時候,會將activeEffect設置為自身,并執(zhí)行相應的回調(diào)函數(shù),函數(shù)的調(diào)用會觸發(fā)到各自使用到的數(shù)據(jù)的getter,將對應的effect依賴注入到每個使用的數(shù)據(jù)上
至于為什么會設置這么復雜的一個屬性的依賴獲取,是因為使用proxy的原因,proxy代理了一整個對象,就不能像vue2使用Obect.defineProperty直接在getter里面就當前的字段進行一個依賴綁定,所以在vue3中是直接將整個對象作為一個Map,每個Map的key都是對應的屬性,而value則是所有依賴當前屬性的對象
set
同get,依舊保持著原先的思路跟模式
- 設置當前數(shù)據(jù)
- 發(fā)布已訂閱的數(shù)據(jù)(觸發(fā)依賴更新)
在vue3中,還是有部分區(qū)別的,畢竟是單獨拉出去的一個庫
- 如果直接調(diào)用effect,當檢測的數(shù)據(jù)發(fā)生變化的時候會直接修改
- 如果調(diào)用watch或者watchEffect,則會走vue自身的調(diào)度方案
所以,如果想當前的數(shù)據(jù)直接可以更新的話, 可以優(yōu)先使用effect,他會比watchEffect的更新速度快一點,劣勢是可能很多東西得自己寫 =,=
至于怎么實現(xiàn)的其實就很簡單了
- 獲取當前更新的數(shù)據(jù)的受依賴項
- 分組進入等待運行的Set中
- 執(zhí)行
但是里面有一個特殊的處理,針對于數(shù)組的length屬性,這個屬性是有一定區(qū)別的,接下來具體講講在vue3中的數(shù)組操作
數(shù)組
在vue2中的,針對數(shù)組是多做了一層處理,代理了數(shù)組的基本方法,這是因為使用Object.defineProperty在數(shù)組上面天然存在劣勢
具體原因在vue的文檔中寫的非常清楚了,這里就不詳細敘述了
而在vue3中使用proxy就完美的解決了這個問題,只是因為proxy能夠監(jiān)聽數(shù)組的變化,做個測試
const a = new Proxy([1,2], { get: function(obj, prop) { console.log('get', obj, prop); return Reflect.get(obj, prop); }, set: function(obj, prop, value) { console.log('set', obj, prop, value); return Reflect.set(obj, prop, value); }, }); a.push(1); get [1,2] push get [1,2] length set [1,2] 2 1 set [1,2, 1] length 3
當我們代理了一個數(shù)組之后,直接調(diào)用push插入一個新的數(shù)據(jù),能夠明顯的看到getter跟setter都會被調(diào)用兩次,一次是調(diào)用的push方法,而另一次是數(shù)組的長度length,也就是說,proxy不僅僅會檢測到我們當前調(diào)用的方法,還能夠知道我們的數(shù)據(jù)長度是否發(fā)生了變化
看到這邊,可能會有一個疑惑,push是對當前數(shù)組進行的操作,但是數(shù)組里面還有部分方法是會返回一個新的數(shù)組,proxy是否會對新生成的數(shù)組也進行代理,這里我們拿splice舉個例子
// a= [1,2] a.splice(0, 1) get [1,2] push get [1,2] length get [1,2] constructor get [1,2] 0 get [1,2] 1 set [1,2] 0 2 set [2,empty] length 1
從表現(xiàn)形式來看,proxy代理之后的數(shù)組只會對當前數(shù)組的內(nèi)容進行監(jiān)聽,也就是調(diào)用splice之后新生成的數(shù)組的變化是不會被代理的
現(xiàn)在我們回過頭來看下vue3的trigger方法,這個是vue在set完成之后觸發(fā)的依賴更新,同樣的掐個頭去個尾,除去正常的執(zhí)行以外,我們看下針對數(shù)組做的優(yōu)化
// add方法是將當前的依賴項添加進一個等待更新的數(shù)組中 else if (key === 'length' && isArray(target)) { depsMap.forEach((dep, key) => { if (key === 'length' || key >= (newValue as number)) { add(dep) } }) }
由于我們知道, 在一次操作數(shù)組的時候會進行多次的set,那么如果每次set都要去更新依賴的話,會造成性能上的浪費,所以在vue3里面只有在set length的時候才會去調(diào)用add方法,然后統(tǒng)一執(zhí)行所有的更新
結語
不得不說,proxy比defineProperty強大了太多,不僅解決了vue的歷史難題,讓vue的體驗更上了一層,更是去除了不少因為defineProperty而必須要的方法,精簡了vue的包大小
雖然proxy的兼容性是比defineProperty差不少,但是在vue里面基本已經(jīng)拋棄了IE,所以如果你的項目需要在ie下運行的話,那就請放棄vue這個選擇,使用低版本的react的吧,哈哈哈哈哈哈
在移動端里面基本上就是沒有這種版本的限制,實在是版本低不能使用proxy的話,相信去找找polyfill是能夠找到的
到此這篇關于vue3為什么要用proxy替代defineProperty的文章就介紹到這了,更多相關vue3 proxy替代defineProperty內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
使用vue-resource進行數(shù)據(jù)交互的實例
下面小編就為大家?guī)硪黄褂胿ue-resource進行數(shù)據(jù)交互的實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09el-descriptions引入代碼中l(wèi)abel不生效問題及解決
這篇文章主要介紹了el-descriptions引入代碼中l(wèi)abel不生效問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-12-12vue通過過濾器實現(xiàn)數(shù)據(jù)格式化
這篇文章主要介紹了vue通過過濾器實現(xiàn)數(shù)據(jù)格式化的方法,文中講解非常細致,幫助大家更好的理解和學習,感興趣的朋友可以了解下2020-07-07VUE2.0+ElementUI2.0表格el-table循環(huán)動態(tài)列渲染的寫法詳解
這篇文章主要介紹了VUE2.0+ElementUI2.0表格el-table循環(huán)動態(tài)列渲染的寫法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2018-11-11