19_Vue如何監測到對象類型數據發生改變的?

數據更新

關於監視

  • 我們之前講過,我們在data當中配置的屬性,最終會掛載在vue實例身上,而data這個配置項,最終也會在vue身上成為一個新的屬性 == _data
    • image-20221029174302808
  • 當我們在頁面DOM當中,去使用data當中的屬性的時候,屬性值發生變化,頁面是不是會自動更新? 為什麼會這樣?
  • 你可以理解為 Vue底層默認有一個監視器,負責監視這些屬性的變化
  • watchcomputed不同,這個監視是全局的,watch與computed是針對單獨的,或者一些屬性

不過目前可以說一句,watch與vue底層監視,用的是一套類似的邏輯

檢測數據的原理

這個概念是非常重要的,所以這節課是不能跳過的,否則有一天會為這個行為買單(謝謝,已經買過了)

我們先來做個需求吧,這個需求不演示不行

  1. 你寫一段程式碼,這個程式碼你需要修改data當中的數據
  2. 修改數據的行為,不能被vue檢測到,也就是卡bug。
  3. 請你實現這個功能

準備工作

這是我的html設計,ul 當中的li標籤渲染data當中persons這個數組的數據

image-20221101164407586

image-20221101164444632

button按鈕呢,設置了一個點擊事件,這個點擊事件用來單獨修改 馬冬梅這個對象的資訊

image-20221101164505759

測試結果

可以看到,在這種賦值的情況下,我們成功的對 馬冬梅 進行了修改數據

並且在vue當中也能檢測到

11-1-2

卡bug,引出問題

既然是 對馬冬梅進行修改,我們換一種方式來對他進行修改

11-1-3

為什麼這次數據修改不成功了,這是為什麼?

總結

  • 當我點擊這個按鈕的時候,在記憶體當中,persons[0]的數據確實發生改變了
  • 但是,這次修改並沒有被vue所檢測到
  • 至於控制台的數據到底修補修改,取決於你什麼時候打開開發者工具

檢測原理

image-20221101165614093

vue是如何檢測對象數據改變的

  • 我們先回顧一下關於vue的數據監測,詳細博文
  • 我現在data這裡有一個屬性name和屬性persons
    • image-20221101165925946
  • 打開控制台,在vue實例身上也有這倆屬性
    • image-20221101170017797
  • 我們都知道,為什麼這倆data當中屬性會出現在vue實例身上,是因為做了數據代理
  • 在vue身上有個_data,這個下劃線data當中包含著我們上圖配置的data的所有數據,並且還對這個配置項data做了加工
    • image-20221101170237817
  • 因為如果只是 將 data的值,賦給_data,那麼二者的內容應該是相等的才是
  • 但是現在顯然不是,說明這裡做了加工
  • 為啥他要加工?
    • 它加工了就可以做響應式了

關於definedproperty

  • 之前說過,vue的數據代理definedProperty 這個API有關
  • 那麼其內部是如何進行數據代理的呢?
  • 如果不使用 vue框架,我們能實現數據代理嗎?
  • 我們來測試一下

錯誤測試

  • 按照正常的理解,如果我們需要對age這個屬性進行數據代理
  • 讓頁面能夠檢測到數據的改變,那麼就需要使用這個介面(defined……)
  • 那麼這個介面的調用,需要如下幾個配置
    • 要給誰添加屬性
    • 屬性名是什麼
    • 配置項(getter和setter)
  • 那麼對getter而言,如果該屬性被訪問到了,那麼就需要返回該屬性的值
    • image-20221102101919433
  • 對setter而言,當屬性值,發生修改,那麼將接收到的修改的屬性值,重新賦值給該屬性

image-20221102101738131

我們雖然添加的是age,但是這裡的意思是將原有屬性age覆蓋掉,使用這個新的age

我們來看下測試結果

11-2-1

出現bug的原因

  • 其實這個問題很好理解,我們看下錯誤原因
    • image-20221102102329508
  • 這是一個 無限遞歸產生的bug,該方法一直無限的被調用,從而產生了這個錯誤
  • 為什麼呢?
  • 我們仔細看下這段程式碼
    1. image-20221102102652200
    2. 當,age屬性被訪問的時候,會調用get函數
    3. 調用get函數,會返回age
    4. 返回的過程當中,age是不是又被訪問了
    5. 從而產生死循環,無限遞歸
  • 為什麼無法修改屬性呢?也是這個道理

所以,vue底層的數據代理,或者說數據加工沒有我們想的這麼簡單,那麼人家是怎麼實現的呢

Observer

  • 在vue當中,有個介面叫做Observer,這個介面用來監視頁面數據發生的變化
  • 不過他底層是如何進行監聽的呢
  • 我們寫不到底層那麼詳細,只寫主要的部分

準備工作

1、首先我們準備一個data,這裡面存放了兩個屬性,name和age

image-20221102112252052

2、我們創建一個function ==> Observer

image-20221102112336622

然後實例化這個 Observer,js當中,function是可以當做構造函數使用的

image-20221102112446513

該函數需要一個屬性,從參數名可以看出,這是一個對象屬性

3、現在我們就來配置這個對象,首先我們需要獲取到 data這個對象當中的所有key值

image-20221102112642593

4、對這個數組,進行循環

image-20221102112734575

5、在迭代的過程當中,使用definedProperty進行數據代理

image-20221102112845830

參數解析,為什麼這裡,添加數據的對象(參數1) 是 this

  1. 使用this,那麼就是給 this所指向的對象 ==> Observer;也就是我們剛剛實例化出來的對象
    • image-20221102113338407
  2. 給它添加屬性(property參數
  3. 那麼接下來我們就在 參數三 當中配置get和set了

6、get和set

image-20221102113208304

數組當中是可以用字元串來獲取元素值的(很少)

完整程式碼

// 這有一個對象,對象有兩個屬性
    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
                }
            })
        })
    }

總結

  1. 我們這裡設置了一個data
    • image-20221102115237706
  2. 通過我們的一系列配置,data身上有的屬性,Observer實例身上也有
  3. 並且,這個observer身上的屬性都做了數據代理
  4. 當然,vue寫的比我們完善的多
  5. 比如,如果data當中還存在對象怎麼辦?

image-20221102115801541

vue在這裡寫了遞歸,一直找,找到這個屬性不再是對象為止

image-20221102120105239

數組也是一個道理,vue也能給你找出來,不過 關於數組和對象的代理,這二者的處理方式不同,下節會講解