深入詳解Vue3 ref底層實現(xiàn)原理
前言
隨著現(xiàn)在vue3越來越普及,相應的面試題也多了起來
說到vue3的面試題,有一個最經(jīng)典的就是ref和reactive的區(qū)別,用法上的區(qū)別很明顯,大家都理解,對于實現(xiàn)這兩個方法的底層原理網(wǎng)上卻眾說紛壇,各有說法。
其中vue3的reactive的是用Proxy實現(xiàn)的這一點是明確的
這里講的就是這個ref,有說是使用Object.defineProperty實現(xiàn)的,有說是使用Proxy實現(xiàn)的,說法不一,到底哪些是正確的呢
下面說一下我自己的理解,如有誤請在評論區(qū)指出!謝謝
源碼解析
既然各方說法不一,那首先想到的就是直接去看vue3官方源碼不就好了,看別人不如自己實際動起來
通過node_modules依賴文件找到vue中實現(xiàn)ref的源碼
源碼如下:
為方便理解,把相關(guān)涉及到的源碼以及代碼含義加上注釋
function ref(value) { // ref方法
return createRef(value, false);
}
function shallowRef(value) { // 淺層ref
return createRef(value, true);
}
function createRef(rawValue, shallow) { // 創(chuàng)建ref
if (isRef(rawValue)) { // 判斷是否為ref
return rawValue;
}
return new RefImpl(rawValue, shallow); // 返回RefImpl實例對象
}
class RefImpl {
constructor(value, __v_isShallow) { // 值,是否淺層ref
this.__v_isShallow = __v_isShallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value); // 判斷是否為淺層ref,否則調(diào)用toReactive,方法在下面
}
get value() { // getter方法 獲取value值
trackRefValue(this);
return this._value;
}
set value(newVal) { // setter方法 設置value值
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal); // 在value值更新時進行判斷是否為淺層ref,否則調(diào)用toReactive
triggerRefValue(this, newVal);
}
}
}
const toReactive = (value) => isObject(value) ? reactive(value) : value; // 是否為對象,如果是則調(diào)用reactive
經(jīng)過一番閱讀理解,我們只需抓取關(guān)鍵信息即可
可以看到通過 ref(1) 的值在 RefImpl 類中為 _value ,然后使用class中的get和set語法糖對該value值進行相應的操作,獲取和賦值
再判斷為對象時才使用reactive()方法
實踐操作
從源碼上看,我們看到了,使用了class的get和set來對這個value值進行操作,那么我們自己動手實踐一下,看看怎么實現(xiàn)
這里把源碼的_value的口頭約定私有屬性形式改為es9新增加的#value形式
class RefImpl {
#value = '' // #value 私有屬性
constructor(value) {
this.#value = value
}
get value() {
console.log('觸發(fā)獲取', this.#value)
return this.#value
}
set value(newVal) {
console.log('觸發(fā)更新', newVal)
this.#value = newVal
}
}
function ref(value) {
return new RefImpl(value)
}
const test = ref('我是小濤測試')
setTimeout(() => {
test.value = '我設置了值'
}, 2000)
可以看到,這個簡單的實例,也可以劫持數(shù)據(jù)的更新方便我們進行其他操作
class類的get和set是什么
到了這里,可以確定ref是使用class里的get/set進行數(shù)據(jù)劫持和更新的
而這個get/set實際是語法糖,本質(zhì)是js的特性,是劫持property(屬性)的一種方式
對象內(nèi)分為數(shù)據(jù)屬性和訪問器屬性,訪問器屬性不包含數(shù)據(jù),是一對get和set方法
Getter 屬性訪問器(accessor)和 Setter 屬性修改器(mutator)
結(jié)論
綜上所述
所以一剛開始說的使用Object.defineProperty說法并不正確,因為Object.defineProperty()可以用來給修改對象屬性,然后使用到了getter/setter
所以使用了class的說法也并不正確,也是在對象內(nèi)使用訪問器屬性,使用到了getter/setter這兩個方法
Object.defineProperty() 方法會直接在一個對象上定義一個新屬性,或者修改一個對象的現(xiàn)有屬性,并返回此對象。
Vue 將遍歷此對象所有的屬性,并使用Object.defineProperty把這些屬性全部轉(zhuǎn)為 getter/setter。
附上一張官方說法 官方鏈接

在對象內(nèi)也可以使用訪問器屬性
const test = {
_value: '',
get value() {
console.log('觸發(fā)獲取', this._value)
return this._value
},
set value(newVal) {
console.log('觸發(fā)更新', newVal)
this._value = newVal
}
}最后,其實簡化到最后發(fā)現(xiàn),都不會難以理解,所以保持探索態(tài)度,多看多學,方是正途
到此這篇關(guān)于深入詳解Vue3 ref底層實現(xiàn)原理的文章就介紹到這了,更多相關(guān)Vue3 ref內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue簡單封裝axios之解決post請求后端接收不到參數(shù)問題
這篇文章主要介紹了Vue簡單封裝axios之解決post請求后端接收不到參數(shù)問題,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2020-02-02
Vue文件如何轉(zhuǎn)換成base64并去除多余的文件類型前綴
這篇文章主要介紹了Vue文件如何轉(zhuǎn)換成base64并去除多余的文件類型前綴問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-03-03
Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別
這篇文章主要介紹了Vue Computed中g(shù)et和set的用法及Computed與watch的區(qū)別,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
后臺使用freeMarker和前端使用vue的方法及遇到的問題
這篇文章主要介紹了后臺使用freeMarker和前端使用vue的方法及遇到的問題,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-06-06
vue中 process.env與process.VUE_CLI_SERVICE詳解
這篇文章主要介紹了vue中process.env與process.VUE_CLI_SERVICE的相關(guān)資料,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05

