Vue3在偉大祖國70週歲時終於和大家見面了。?
在學習Vue3的資料繫結前,先預習一下相關知識:
1、Proxy,我之前寫過一篇Proxy介紹,可以參考下。
2、WeakMap
然後我們就能愉快的玩耍了。
Vue3中關於資料繫結這塊的程式碼主要在這裡
我們主要關注reactive.ts和baseHandlers.ts這兩個檔案裡的內容。
1.Proxy已知的兩個問題
Proxy本身不支援物件內部的深度偵測,需要自己實現
Proxy本身支援陣列變化偵測,但會有多次觸發的風險
2.Vue3是如何解決
2-1
解決第一個問題,對於初學者,我們可以採用暴力遞迴的辦法,一開始就把物件內部的所有物件都用Proxy代理一遍。
這樣操作的結果就是,proxy變成下圖所示的結果:
可以看到,物件內部的物件和陣列都已經被代理了。當obj是一個非常大且複雜的物件時,這樣做效能肯定不好,我們來看看Vue是怎麼做的。
Vue中,一開始我們只是把最外層的物件做Proxy操作,而內層的物件,只有在需要訪問的時候,才會惰性地建立Proxy代理。為此,我們需要對handler
函式做一下改造。
完成上述操作後,Vue3還做了一件事,就是把動態建立的Proxy都存在一個WeakMap
中,這樣我們就不用每次執行get
操作的時候都會去動態建立了,畢竟這是很耗費記憶體的。在Vue3原始碼中我們可以有兩個WeakMap
,就是幹這事的。為此,我們還需要改造一下createReactiveObject
方法,傳入兩個WeakMap
來做快取。另外,toRaw
這個WeakMap
幹嘛用的,留給大家思考一下?。
2-2
下面我們來解決第二個問題,假設我們有一個陣列arr=[1,2]
,當我們執行arr.push(3)
時,arr
有兩個屬性發生了變化,一個是新增了一個key=2
的索引屬性,對應value就是我們剛新增的3,這時可以把陣列理解成一個物件;此外arr
的length
屬性也發生了變化,增加了1。所以,如果用Proxy劫持了陣列的set方法時,此時會觸發兩次set操作,一次對應的key是2
,一次對應的key是length
。
每次執行set
操作新增或者修改某個屬性時,我們先利用Object.hasOwnProperty
判斷這個屬性是否存在,如果不存在則直接
trigger add ...
;如果是已有屬性,則判斷當前value
和target
的上相同key
對應的value
,也就是程式碼中的oldValue
是否相等,如果不相等,才會去執行trigger set ...
。這裡需要細細理解一下,當觸發key
為length
的回撥時,由於此時的target
,就是被代理的陣列物件,在上一次key
為2
的回撥觸發時,執行了Reflect.set(target, key, value, receiver)
,此時它的陣列長度已經+1
了,所以在key
為length
的回撥中,此時oldValue
和value
是相等的,都是3
。這樣,我們就避免了多次觸發render
的操作,Vue3中的這段程式碼還是寫的很精妙的,??。
到此,Proxy原生的兩個大問題都被合理解決了,接下來就是一些常規的前置條件判斷,Vue3中只對陣列、物件、Map、WeakMap、Set、WeakSet這幾類資料做了資料繫結,對基本資料型別和一些JS內建物件如Date、Promise和Reg等是直接返回不會做Proxy處理的。
下面我們看一眼本文中用到的demo和Vue3原始碼的對比,可以看到,大體的結構是一樣的,當然demo中做了很多的簡化操作。
3.demo小結
看到這裡,你是不是對Vue3中的資料繫結有了一個大概的瞭解。完整demo程式碼在這裡。寫的不完善的地方,還請各位大佬多多指教。