VUE響應(yīng)式原理的實(shí)現(xiàn)詳解
前言
相信vue學(xué)習(xí)者都會(huì)發(fā)現(xiàn),vue使用起來(lái)上手非常方便,例如雙向綁定機(jī)制,讓我們實(shí)現(xiàn)視圖、數(shù)據(jù)層的快速同步,但雙向綁定機(jī)制實(shí)現(xiàn)的核心數(shù)據(jù)響應(yīng)的原理是怎么樣的呢,接下來(lái)讓我們開始介紹:
function observer(value) {
//給所有傳入進(jìn)來(lái)的data 設(shè)置一個(gè)__ob__對(duì)象 一旦value有__ob__ 說(shuō)明該value已經(jīng)做了響應(yīng)式處理
Object.defineProperty(value, '__ob__', {
value: this, //當(dāng)前實(shí)例 也就是new observer
enumerable: false, //不可枚舉 即不可for in
writable: true, // 可用賦值運(yùn)算符改寫__ob__
configurable: true //可改寫可刪除
})
//這里是判斷是對(duì)象 數(shù)組的話需要改造數(shù)組原型上的方法
if (Object.prototype.toString.call(value) === "[object Array]") {
//數(shù)組的話需要改造數(shù)組原型上的方法 下面會(huì)講解arrayMethods
value.__proto__ = arrayMethods;
//對(duì)數(shù)組進(jìn)行響應(yīng)式處理
observeArray(value);
} else {
//如果是對(duì)象 遍歷對(duì)象屬性進(jìn)行響應(yīng)式處理
iterate(value)
}
}
// 遍歷對(duì)象屬性進(jìn)行響應(yīng)式處理
function iterate(data) {
const keys = Object.keys(data);
keys.forEach((key) => {
defineReactive(data, key, data[key])
})
}
//響應(yīng)式處理 這里是核心
function defineReactive(data, key, value){
//遞歸對(duì)象 這里是因?yàn)閷?duì)象里面仍可能嵌套對(duì)象
observe(value)
//寫道這里 Object.defineProperty 我們主角出場(chǎng)了
// 這里實(shí)現(xiàn)了讀寫都能捕捉到,響應(yīng)式的底層原理
Object.defineProperty(data, key, {
get() {
console.log('我被成功訪問(wèn)啦!');
return value
},
set(newValue) {
if (newValue === value) return
console.log("我被變更啦")
value = newValue
}
})
}
function observeArray(data) {
data.forEach(item => {
observe(item)
})
}
function observe(value) {
// 如果傳進(jìn)來(lái)的是對(duì)象或者數(shù)組,則進(jìn)行響應(yīng)式處理
if (Object.prototype.toString.call(value) === '[object Object]' || Object.prototype.toString.call(value) === "[object Array]") {
return new Observer(value)
}
}
上面代碼簡(jiǎn)單的實(shí)現(xiàn)了vue2.0中響應(yīng)式的原理,相信注釋也非常的清晰,總結(jié)一下三個(gè)主要的方法:
| 名稱 | 作用 |
|---|---|
| observer | 觀察者對(duì)象,對(duì)數(shù)組、對(duì)象進(jìn)行響應(yīng)式處理 |
| defineReactive | 攔截對(duì)象中的key中的set、get方法 |
| observe | 響應(yīng)式處理的入口 |
從上面的大致實(shí)現(xiàn)方法中,我們不難看出幾個(gè)問(wèn)題:
1.使用defineProperty,我們無(wú)法實(shí)現(xiàn)對(duì)象刪除的監(jiān)聽、以及新增對(duì)象屬性的時(shí)候,set方法沒有被調(diào)用,下圖是實(shí)驗(yàn)結(jié)果


2.數(shù)組修改只能通過(guò)改寫的方法,無(wú)法通過(guò)arr[index] = xxx 進(jìn)行修改,也無(wú)法通過(guò)length屬性進(jìn)行修改,下圖是輸出結(jié)果:

解決方案
針對(duì)上面的問(wèn)題,vue提出了自己的解決方案:
$set(obj, key, value),原理相信大家不難猜出,通過(guò)hack的方式,對(duì)象的處理方法是重新為對(duì)象賦值,而數(shù)組是通過(guò)splice來(lái)轉(zhuǎn)換為響應(yīng)式
function set (target, key, val) {
//isValidArrayIndex 用來(lái)檢測(cè)是否合法索引
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key);
target.splice(key, 1, val);
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val;
return val
}
//...
defineReactive$$1(ob.value, key, val);
ob.dep.notify();
return val
}
數(shù)組的特殊處理
相信大家還發(fā)現(xiàn),數(shù)組做了特殊處理,上面的代碼也寫到?jīng)]有使用遍歷使用defineProperty去監(jiān)聽數(shù)據(jù),修改數(shù)組原型上的部分方法,來(lái)實(shí)現(xiàn)修改數(shù)組觸發(fā)響應(yīng)式,也就是上面代碼的arrayMethods,我們接著來(lái)看這個(gè)的具體實(shí)現(xiàn)思路:
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(method =>{
// 緩存原來(lái)的方法
def(arrayMethods, method)
})
function def(obj, key) {
Object.defineProperties(obj, key, {
enumerable: true,
configurable: true,
value: function (...args) {
//獲取數(shù)組原生方法
let original = arrayProto[key];
//改變this指向
const result = original.apply(this, args)
console.log('我被更新了');
//result就是上文的arrayMethods
return result;
}
})
}
這里大概分為三個(gè)思路
1.獲取數(shù)組原型上的方法
2.使用defineProperties對(duì)數(shù)組原型上的方法進(jìn)行劫持
3.把需要被改造的 Array 原型方法指向改造后原型。
這樣做的好處
沒有直接修改 Array.prototype,而是直接把 arrayMenthods 賦值給 value 的 proto 。因?yàn)檫@樣不會(huì)污染全局的Array, arrayMenthods 只對(duì) data中的Array 生效。
題外話
關(guān)于數(shù)組為什么不使用defineProperties進(jìn)行劫持,網(wǎng)上大部分說(shuō)法都是覺得開銷太大,因?yàn)樵谖覀儤I(yè)務(wù)場(chǎng)景中一般的對(duì)象不會(huì)有太多屬性,但列表中幾千、上萬(wàn)條數(shù)據(jù)確是很正常,這一點(diǎn)也可以講通。
總結(jié)
感謝你的閱讀,在vue3.0中使用proxy進(jìn)行數(shù)據(jù)劫持后,都說(shuō)解決了2.0存在的問(wèn)題以及提升了效率,后面我也會(huì)完善3.0響應(yīng)式的實(shí)現(xiàn)原理。
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Vue3.x+Element Plus仿制Acro Design簡(jiǎn)潔模式實(shí)現(xiàn)分頁(yè)器組件
開發(fā)中難免會(huì)遇到寬度很窄的列表需要使用分頁(yè)器的情況。本文將利用Vue3.x+Element Plus仿制Acro Design簡(jiǎn)潔模式實(shí)現(xiàn)分頁(yè)器組件,感興趣的可以了解一下2023-02-02
Vue默認(rèn)插槽,具名插槽,作用域插槽定義及使用方法
這篇文章主要介紹了Vue默認(rèn)插槽,具名插槽,作用域插槽定義及使用方法,插槽的作用是在子組件中某個(gè)位置插入父組件的自定義html結(jié)構(gòu)和data數(shù)據(jù),下面詳細(xì)內(nèi)容需要的小伙伴可以參考一下2022-03-03
解決el-upload批量上傳只執(zhí)行一次成功回調(diào)on-success的問(wèn)題
這篇文章主要介紹了解決el-upload批量上傳只執(zhí)行一次成功回調(diào)on-success的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Nuxt3+ElementPlus構(gòu)建打包部署全過(guò)程
網(wǎng)上大部分關(guān)于Nuxt打包部署教程可謂是可以用五花八門來(lái)形容,這對(duì)于第一次接觸的朋友簡(jiǎn)直是無(wú)從下手,這篇文章主要給大家介紹了關(guān)于Nuxt3+ElementPlus構(gòu)建打包部署的相關(guān)資料,需要的朋友可以參考下2023-01-01
vue如何判斷數(shù)組中的對(duì)象是否包含某個(gè)值
這篇文章主要介紹了vue如何判斷數(shù)組中的對(duì)象是否包含某個(gè)值,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08

