19_Vue如何監測到對象類型數據發生改變的?
數據更新
關於監視
- 我們之前講過,我們在data當中配置的屬性,最終會掛載在vue實例身上,而data這個配置項,最終也會在vue身上成為一個新的屬性 == _data
- 當我們在頁面DOM當中,去使用data當中的屬性的時候,屬性值發生變化,頁面是不是會自動更新? 為什麼會這樣?
- 你可以理解為 Vue底層默認有一個監視器,負責監視這些屬性的變化
- 與watch和computed不同,這個監視是全局的,watch與computed是針對單獨的,或者一些屬性
不過目前可以說一句,watch與vue底層監視,用的是一套類似的邏輯
檢測數據的原理
這個概念是非常重要的,所以這節課是不能跳過的,否則有一天會為這個行為買單(謝謝,已經買過了)
我們先來做個需求吧,這個需求不演示不行
- 你寫一段程式碼,這個程式碼你需要修改data當中的數據
- 修改數據的行為,不能被vue檢測到,也就是卡bug。
- 請你實現這個功能
準備工作
這是我的html設計,ul 當中的li標籤渲染data當中persons這個數組的數據
button按鈕呢,設置了一個點擊事件,這個點擊事件用來單獨修改 馬冬梅這個對象的資訊
測試結果
可以看到,在這種賦值的情況下,我們成功的對 馬冬梅 進行了修改數據
並且在vue當中也能檢測到
卡bug,引出問題
既然是 對馬冬梅進行修改,我們換一種方式來對他進行修改
為什麼這次數據修改不成功了,這是為什麼?
總結
- 當我點擊這個按鈕的時候,在記憶體當中,persons[0]的數據確實發生改變了
- 但是,這次修改並沒有被vue所檢測到
- 至於控制台的數據到底修補修改,取決於你什麼時候打開開發者工具
檢測原理
vue是如何檢測對象數據改變的
- 我們先回顧一下關於vue的數據監測,詳細博文
- 我現在data這裡有一個屬性name和屬性persons
- 打開控制台,在vue實例身上也有這倆屬性
- 我們都知道,為什麼這倆data當中屬性會出現在vue實例身上,是因為做了數據代理
- 在vue身上有個_data,這個下劃線data當中包含著我們上圖配置的data的所有數據,並且還對這個配置項data做了加工
- 因為如果只是 將 data的值,賦給_data,那麼二者的內容應該是相等的才是
- 但是現在顯然不是,說明這裡做了加工
- 為啥他要加工?
- 它加工了就可以做響應式了
關於definedproperty
- 之前說過,vue的數據代理與 definedProperty 這個API有關
- 那麼其內部是如何進行數據代理的呢?
- 如果不使用 vue框架,我們能實現數據代理嗎?
- 我們來測試一下
錯誤測試
- 按照正常的理解,如果我們需要對age這個屬性進行數據代理
- 讓頁面能夠檢測到數據的改變,那麼就需要使用這個介面(defined……)
- 那麼這個介面的調用,需要如下幾個配置
- 需要給誰添加屬性
- 屬性名是什麼
- 配置項(getter和setter)
- 那麼對getter而言,如果該屬性被訪問到了,那麼就需要返回該屬性的值
- 對setter而言,當屬性值,發生修改,那麼將接收到的修改的屬性值,重新賦值給該屬性
我們雖然添加的是age,但是這裡的意思是將原有屬性age覆蓋掉,使用這個新的age
我們來看下測試結果
出現bug的原因
- 其實這個問題很好理解,我們看下錯誤原因
- 這是一個 無限遞歸產生的bug,該方法一直無限的被調用,從而產生了這個錯誤
- 為什麼呢?
- 我們仔細看下這段程式碼
- 當,age屬性被訪問的時候,會調用get函數
- 調用get函數,會返回age
- 返回的過程當中,age是不是又被訪問了
- 從而產生死循環,無限遞歸
- 為什麼無法修改屬性呢?也是這個道理
所以,vue底層的數據代理,或者說數據加工沒有我們想的這麼簡單,那麼人家是怎麼實現的呢
Observer
- 在vue當中,有個介面叫做Observer,這個介面用來監視頁面數據發生的變化
- 不過他底層是如何進行監聽的呢
- 我們寫不到底層那麼詳細,只寫主要的部分
準備工作
1、首先我們準備一個data,這裡面存放了兩個屬性,name和age
2、我們創建一個function ==> Observer
然後實例化這個 Observer,js當中,function是可以當做構造函數使用的
該函數需要一個屬性,從參數名可以看出,這是一個對象屬性
3、現在我們就來配置這個對象,首先我們需要獲取到 data這個對象當中的所有key值
4、對這個數組,進行循環
5、在迭代的過程當中,使用definedProperty進行數據代理
參數解析,為什麼這裡,添加數據的對象(參數1) 是 this?
- 使用this,那麼就是給 this所指向的對象 ==> Observer;也就是我們剛剛實例化出來的對象
- 給它添加屬性(property參數)
- 那麼接下來我們就在 參數三 當中配置get和set了
6、get和set
數組當中是可以用字元串來獲取元素值的(很少)
完整程式碼
// 這有一個對象,對象有兩個屬性
let data = {
name: "waves",
age: 0
}
// 實例化一個監視器對象
let observer = new Observer(data);
// 監視對象Observer
function Observer(obj){
// 1、獲取data當中的所有key值
let properties = Object.keys(data); // ["name","age"]
// 2、迭代 properties數組
properties.forEach((property)=>{
// 3、在迭代的過程當中,使用definedProperty進行數據代理
Object.defineProperty(this,property,{
// 配置get和set
get(){
// 很簡單,因為data沒有做數據代理,返回data[property]即可
return data[property]; // data["name"] = waves
},
set(val){
// 賦值即可
data[property] = val
}
})
})
}
總結
- 我們這裡設置了一個data
- 通過我們的一系列配置,data身上有的屬性,Observer實例身上也有
- 並且,這個observer身上的屬性都做了數據代理
- 當然,vue寫的比我們完善的多
- 比如,如果data當中還存在對象怎麼辦?
vue在這裡寫了遞歸,一直找,找到這個屬性不再是對象為止
數組也是一個道理,vue也能給你找出來,不過 關於數組和對象的代理,這二者的處理方式不同,下節會講解