Vue 的響應式原理是核心是通過 ES5 的保護對象的 Object.defindeProperty 中的訪問器屬性中的 get 和 set 方法,data 中聲明的屬性都被添加了訪問器屬性,當讀取 data 中的數據時自動調用 get 方法,當修改 data 中的數據時,自動調用 set 方法,檢測到數據的變化,會通知觀察者 Wacher,觀察者 Wacher自動觸發重新render 當前組件(子組件不會重新渲染),生成新的虛擬 DOM 樹,Vue 框架會遍歷並對比新虛擬 DOM 樹和舊虛擬 DOM 樹中每個節點的差別,並記錄下來,最后,加載操作,將所有記錄的不同點,局部修改到真實 DOM 樹上。
虛擬DOM (Virtaul DOM): 用 js 對象模擬的,保存當前視圖內所有 DOM 節點對象基本描述屬性和節點間關系的樹結構。用 js 對象,描述每個節點,及其父子關系,形成虛擬 DOM 對象樹結構。
項目中常遇到的關於vue響應式的記錄與總結:
因為只要在 data 中聲明的基本數據類型的數據,基本不存在數據不響應問題,所以重點介紹數組和對象在vue中的數據響應問題,vue可以檢測對象屬性的修改,但無法監聽數組的所有變動及對象的新增和刪除,只能使用數組變異方法及$set方法。
可以看到,arrayMethods 首先繼承了 Array,然后對數組中所有能改變數組自身的方法,如 push、pop 等這些方法進行重寫。重寫后的方法會先執行它們本身原有的邏輯,並對能增加數組長度的 3 個方法 push、unshift、splice 方法做了判斷,獲取到插入的值,然后把新添加的值變成一個響應式對象,並且再調用 ob.dep.notify() 手動觸發依賴通知,這就很好地解釋了用 vm.items.splice(newLength) 方法可以檢測到變化。。
1. 向響應式的數組或者對象中修改已有的屬性的方法
當想要修改對象或者屬性,並非新增屬性時,一個已經在 data 中聲明過的響應式數據,可以直接操作改變,數據改變會經過上圖的步驟,觸發視圖改變。直接obj.xxx = xxx 即可,數組除外,但是后台傳過來的 json 數組,數組中嵌套的對象也可以直接修改數組中的對象,因為 Object.defindeProperty 的缺陷導致無法監聽數組的變動,但始終會深度遍歷data中數據,給數組中嵌套的對象添加上 get 和 set 方法,完成對對象的監聽。所以數組中嵌套的對象的情況是可以直接修改數組中的對象,並且保持響應式。
2. 向響應式的數組或者對象中新增一個響應式的屬性的方法this.$set()或者數組變異方法
即使是一個后台傳過來的 json 數組,也可以使用this.$set向數組中的其中一個對象中添加一個響應式的屬性,例如 this.$set(arr[0], 'xxx', xxx) 。或者使用數組變異方法例如splice,更多數組變異方法可以參考vue文檔。
3. data中聲明過的數組或者對象,整體替換數組或者對象保持響應式
向響應式的數組和對象替換為新的響應式數據,可直接復制,因為data中聲明的數據已經添加了訪問器屬性setter,當重新賦值一個新的堆內存地址時,該數組或者對象也會被循環遍歷添加訪問器屬性,所以也是有響應式的。
4. vue無法監聽對象的新增和刪除,直接通過obj.xxx = xxx新增一個沒有的屬性,同時修改當前組件的一個響應式的數據,會重新觸發當前組件重新render,可以讓非響應式數據也保持更新狀態(並非響應式) 。
給一個數據添加一個非響應式的數據,例如一個已經在data中聲明過的數據obj,obj.xxx=xxx,新增一個原本沒有的數據,同時修改組件中一個其他的響應式數據,該obj也會同步更新到最新的數據,另一種情況,當你向一個對象或者數組中同時增加一個響應式和非響應式數據,非響應式數據也會同步更新到頁面。
總結:只要觸發當前組件重新render,就可以讓數據保持更新的狀態,例如this.$forceUpdate()。
為什么vue不能監聽數組的變化?
Object.defindProperty雖然能夠實現雙向綁定了,但是還是有缺點,只能對對象的屬性進行數據劫持,所以會深度遍歷整個對象,不管層級有多深,只要數組中嵌套有對象,就能監聽到對象的數據變化無法監聽到數組的變化,Proxy就沒有這個問題,可以監聽整個對象的數據變化,所以用vue3.0會用Proxy代替definedProperty。
最后實現一個數據雙向綁定原理
更深的底層原理還在學習中,完全消化以后會繼續分享,嗯,就醬~