19_Vue如何監測到物件型別資料發生改變的?

2022-11-02 18:01:42

資料更新

關於監視

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

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

檢測資料的原理

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

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

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

準備工作

這是我的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,該方法一直無限的被呼叫,從而產生了這個錯誤
  • 為什麼呢?
  • 我們仔細看下這段程式碼
    1. 當,age屬性被存取的時候,會呼叫get函數
    2. 呼叫get函數,會返回age
    3. 返回的過程當中,age是不是又被存取了
    4. 從而產生死迴圈,無限遞迴
  • 為什麼無法修改屬性呢?也是這個道理

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

Observer

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

準備工作

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

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

然後範例化這個 Observer,js當中,function是可以當做建構函式使用的

該函數需要一個屬性,從引數名可以看出,這是一個物件屬性

3、現在我們就來設定這個物件,首先我們需要獲取到 data這個物件當中的所有key值

4、對這個陣列,進行迴圈

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

引數解析,為什麼這裡,新增資料的物件(引數1) 是 this

  1. 使用this,那麼就是給 this所指向的物件 ==> Observer;也就是我們剛剛範例化出來的物件
  2. 給它新增屬性(property引數
  3. 那麼接下來我們就在 引數三 當中設定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
                }
            })
        })
    }

總結

  1. 我們這裡設定了一個data
  2. 通過我們的一系列設定,data身上有的屬性,Observer範例身上也有
  3. 並且,這個observer身上的屬性都做了資料代理
  4. 當然,vue寫的比我們完善的多
  5. 比如,如果data當中還存在物件怎麼辦?

vue在這裡寫了遞迴,一直找,找到這個屬性不再是物件為止

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