VUE響應式原理的實現(xiàn)詳解
前言
相信vue學習者都會發(fā)現(xiàn),vue使用起來上手非常方便,例如雙向綁定機制,讓我們實現(xiàn)視圖、數(shù)據(jù)層的快速同步,但雙向綁定機制實現(xiàn)的核心數(shù)據(jù)響應的原理是怎么樣的呢,接下來讓我們開始介紹:
function observer(value) { //給所有傳入進來的data 設置一個__ob__對象 一旦value有__ob__ 說明該value已經(jīng)做了響應式處理 Object.defineProperty(value, '__ob__', { value: this, //當前實例 也就是new observer enumerable: false, //不可枚舉 即不可for in writable: true, // 可用賦值運算符改寫__ob__ configurable: true //可改寫可刪除 }) //這里是判斷是對象 數(shù)組的話需要改造數(shù)組原型上的方法 if (Object.prototype.toString.call(value) === "[object Array]") { //數(shù)組的話需要改造數(shù)組原型上的方法 下面會講解arrayMethods value.__proto__ = arrayMethods; //對數(shù)組進行響應式處理 observeArray(value); } else { //如果是對象 遍歷對象屬性進行響應式處理 iterate(value) } } // 遍歷對象屬性進行響應式處理 function iterate(data) { const keys = Object.keys(data); keys.forEach((key) => { defineReactive(data, key, data[key]) }) } //響應式處理 這里是核心 function defineReactive(data, key, value){ //遞歸對象 這里是因為對象里面仍可能嵌套對象 observe(value) //寫道這里 Object.defineProperty 我們主角出場了 // 這里實現(xiàn)了讀寫都能捕捉到,響應式的底層原理 Object.defineProperty(data, key, { get() { console.log('我被成功訪問啦!'); return value }, set(newValue) { if (newValue === value) return console.log("我被變更啦") value = newValue } }) } function observeArray(data) { data.forEach(item => { observe(item) }) } function observe(value) { // 如果傳進來的是對象或者數(shù)組,則進行響應式處理 if (Object.prototype.toString.call(value) === '[object Object]' || Object.prototype.toString.call(value) === "[object Array]") { return new Observer(value) } }
上面代碼簡單的實現(xiàn)了vue2.0中響應式的原理,相信注釋也非常的清晰,總結(jié)一下三個主要的方法:
名稱 | 作用 |
---|---|
observer | 觀察者對象,對數(shù)組、對象進行響應式處理 |
defineReactive | 攔截對象中的key中的set、get方法 |
observe | 響應式處理的入口 |
從上面的大致實現(xiàn)方法中,我們不難看出幾個問題:
1.使用defineProperty,我們無法實現(xiàn)對象刪除的監(jiān)聽、以及新增對象屬性的時候,set方法沒有被調(diào)用,下圖是實驗結(jié)果
2.數(shù)組修改只能通過改寫的方法,無法通過arr[index] = xxx 進行修改,也無法通過length屬性進行修改,下圖是輸出結(jié)果:
解決方案
針對上面的問題,vue提出了自己的解決方案:
$set(obj, key, value)
,原理相信大家不難猜出,通過hack的方式,對象的處理方法是重新為對象賦值,而數(shù)組是通過splice來轉(zhuǎn)換為響應式
function set (target, key, val) { //isValidArrayIndex 用來檢測是否合法索引 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ù)組原型上的部分方法,來實現(xiàn)修改數(shù)組觸發(fā)響應式,也就是上面代碼的arrayMethods
,我們接著來看這個的具體實現(xiàn)思路:
const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(method =>{ // 緩存原來的方法 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; } }) }
這里大概分為三個思路
1.獲取數(shù)組原型上的方法
2.使用defineProperties對數(shù)組原型上的方法進行劫持
3.把需要被改造的 Array 原型方法指向改造后原型。
這樣做的好處
沒有直接修改 Array.prototype,而是直接把 arrayMenthods 賦值給 value 的 proto 。因為這樣不會污染全局的Array, arrayMenthods 只對 data中的Array 生效。
題外話
關于數(shù)組為什么不使用defineProperties進行劫持,網(wǎng)上大部分說法都是覺得開銷太大,因為在我們業(yè)務場景中一般的對象不會有太多屬性,但列表中幾千、上萬條數(shù)據(jù)確是很正常,這一點也可以講通。
總結(jié)
感謝你的閱讀,在vue3.0中使用proxy進行數(shù)據(jù)劫持后,都說解決了2.0存在的問題以及提升了效率,后面我也會完善3.0響應式的實現(xiàn)原理。
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關注腳本之家的更多內(nèi)容!
相關文章
Vue3.x+Element Plus仿制Acro Design簡潔模式實現(xiàn)分頁器組件
開發(fā)中難免會遇到寬度很窄的列表需要使用分頁器的情況。本文將利用Vue3.x+Element Plus仿制Acro Design簡潔模式實現(xiàn)分頁器組件,感興趣的可以了解一下2023-02-02解決el-upload批量上傳只執(zhí)行一次成功回調(diào)on-success的問題
這篇文章主要介紹了解決el-upload批量上傳只執(zhí)行一次成功回調(diào)on-success的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03