零基礎入門Vue之畫龍點睛——再探監測數據

来源:https://www.cnblogs.com/Star-Vik/p/18009891
-Advertisement-
Play Games

追憶 上一節:零基礎入門Vue之影分身之術——列表渲染&渲染原理淺析 雖然我深知,大佬告訴我”先學應用層在瞭解底層,以應用層去理解底層“,但Vue的數據如何檢測的我不得不去學 否則,在寫代碼的時候,可能會出現我難以解釋的bug 對此,本篇文章,將記錄我對Vue檢測數據的理解 對於Vue檢測數據的實現 ...


追憶

上一節:零基礎入門Vue之影分身之術——列表渲染&渲染原理淺析

雖然我深知,大佬告訴我”先學應用層在瞭解底層,以應用層去理解底層“,但Vue的數據如何檢測的我不得不去學

否則,在寫代碼的時候,可能會出現我難以解釋的bug

對此,本篇文章,將記錄我對Vue檢測數據的理解


對於Vue檢測數據的實現,我打算由淺入深的去記錄

  1. JavaScript實現數據監控
  2. 實現簡單的數據監測(淺淺的響應式)
  3. Vue對哪些數據做了監測,哪些沒有?

JavaScript的數據檢測

Object.defineProperty() 靜態方法會直接在一個對象上定義一個新屬性,或修改其現有屬性,並返回此對象。

熟悉JavaScript的人,應該在一個月黑風高的夜晚都瞭解過Object上的一個方法: Object.defineProperty()

而Vue大部分的數據監測都是依賴於這個方法來實現的

ps:本篇不會深度探討這個靜態方法的用法,僅僅對其get和set方法的用法講解,為下一章節做鋪墊

Object.defineProperty(obj, prop, descriptor)
  • obj:要添加新屬性的對象
  • prop:要添加屬性的名稱(一般為字元串)
  • descriptor:這個屬性的相關描述(配置)

假設現在有一個對象如下:

let person = {
  name:"張三"
};

現在,我試圖這個person對象新增一個age屬性,那麼我可以這麼乾

Object.defineProperty(person, "age", {
  value:18 //設置預設的值
});

此時輸出person時可以看到

{name: '張三', age: 18}

題外話:關於為什麼是點開後為什麼age屬性與其他屬性顏色不一樣,可以將enumerable:true,使它可迭代

但這真的是一個普通屬性了嗎?並不會,當我試圖

person.age = 20
console.log(person.age); //18

似乎修改不了數據,因為少了一個配置項

Object.defineProperty(person, "age", {
    value:18,
    writable:true //配置為可修改
});

此時上述代碼差不多等同於

person.age = 18; //假設age沒定義過,重新給person追加一個屬性

get

用作屬性 getter 的函數,如果沒有 getter 則為 undefined。當訪問該屬性時,將不帶參地調用此函數,並將 this 設置為通過該屬性訪問的對象(因為可能存在繼承關係,這可能不是定義該屬性的對象)。返回值將被用作該屬性的值。預設值為 undefined。


從上面MDN上的話來說,當我們要用到對象上的某個屬性時會調用 getter,如果對象沒有設置,則預設是undefined,當訪問這個屬性時,訪問得到的結果將是getter返回的結果

我現在有一個需求:當age屬性被讀取時,就加1歲,並且輸出變化後的值

在上述代碼裡面是完不成這個需求的,此時就需要用到get方法了,當讀取時會自動調用get方法,我可以在那個裡面進行數據的遞增

註意:官網描述中,get不能與 value 或 writable 同時使用

  1. 定義實際年齡數據:_age
  2. 當讀取person.age,get選擇器返回_age,並且把_age遞增

具體實現代碼如下:

let age = 18; //定義一個實際的年齡數據
let person = { //準備好目標對象
  name:"張三"
};

Object.defineProperty(person,"age",{
  get(){
    console.log("年齡即將遞增一歲後:",age + 1);
    return age,age++;
  }
});
> person.age
年齡即將遞增一歲後: 19
18
> person.age
年齡即將遞增一歲後: 20
19

綜上所述,當有人要讀取某個屬性的時候,可以對這個屬性值做了處理在返回,或者是調用函數通知其他的事件觸發等等


set

用作屬性 setter 的函數,如果沒有 setter 則為 undefined。當該屬性被賦值時,將調用此函數,並帶有一個參數(要賦給該屬性的值),並將 this 設置為通過該屬性分配的對象。預設值為 undefined。


set的用法正好和get方法對應,一個是讀一個是寫,set方法當要給屬性賦值時會被調用,並且可以接收一個參數作為新的值

同樣以這個對象為例,還是把get賦值寫好,每次都返回當前age的值

let age = 18; //定義一個實際的年齡數據
let person = { //準備好目標對象
  name:"張三"
};

Object.defineProperty(person,"age",{
  get(){
    return age;
  }
});

現在呢,如果我去修改他得值,他還是出現改不掉的情況

這是因為set沒配置,我先配置個最基本的set看看,能否修改age的值

let age = 18; //定義一個實際的年齡數據
let person = { //準備好目標對象
  name:"張三"
};

Object.defineProperty(person,"age",{
  get(){
    return age; //返回實際年齡
  },
  set(newVal){
      age = newVal //修改實際年齡
  }
});
> person.age
18
> person.age = 19
19
> person.age
19

很顯然是可以修改的

現在呢,我希望當我對年齡做出了修改,如果不是遞增1的話就彈出警告

那麼我可以這麼乾

let age = 18; //定義一個實際的年齡數據
let person = { //準備好目標對象
  name:"張三"
};

Object.defineProperty(person,"age",{
  get(){
    return age;
  },
  set(newVal){
      if(newVal !== age+1){
          console.warn("註意:你這個年齡增加的有點快啊!!!");
      }
      age = newVal
  }
});

此時這段代碼,能完美的完成需求


畫龍點睛

上面的例子,還是不怎麼完善,萬一有人給年齡隨意賦值呢?那我是不是要彈出報錯?

所以,當調用set的時候,可以進行一系列的數據類型判斷,這裡僅需判斷是否為數值即可,區別不能為負值不然就拋出錯誤

代碼如下:

let age = 18; //定義一個實際的年齡數據
let person = { //準備好目標對象
  name:"張三"
};

Object.defineProperty(person,"age",{
  get(){
    return age;
  },
  set(newVal){
      if(typeof newVal !== 'number'){
          throw "你在想什麼呢?";
      }else if(newVal < 0){
          throw "你跟閻王溝通過?";
      }else if(newVal !== age+1){
          console.warn("註意:你這個年齡增加的有點快啊!!!");
      }
      age = newVal;
  }
});

實現簡單的數據檢測

在第一篇:零基礎入門Vue之夢開始的地方——插值語法 中我提到如下的說明

"{{}}"在這個表達式裡面可以寫js的表達式,並且它裡面的執行語句的this是vue實例,同時vue官方文檔指出,在data中配置的東西最後都會通過數據代理的方式掛在到vue實例上。

data配置項裡面的所有數據,都會以數據代理的方式掛在到vm實例上,並且Vue也會提供一個純凈版的Vue._data,此時這個Vue._data等同於我們配置的data
(即:vm._data === data is true)

而這個數據代理就是依賴於上一節說的 Object.defineProperty() 來實現


實現原理簡單分析&實現

在Vue中,Vue對實際傳入的data並沒有直接掛在到vm對象及vm._data上,而是重新通過get和set去做一系列的數據代理和數據監測

這個過程中有許多細節要處理,本篇不可能以這一千不到的字數去說明白Vue的數據檢測和數據代理

僅僅只是做一個基本的樣例,供我自己學習


首先,我得準備一個方法用來刷新dom意思意思一下

function flashVirtualDom(){
  //此處省略新老虛擬dom之間的比較演算法
  console.log("檢測到數據更改,準備刷新虛擬dom");
}

然後呢,我要準備好一個數據

let data = {
    name:"張三",
    age:18,
    friends:["李四","王五","趙高"],
    school:{
        name:"北京大學",
        local:"北京",
        totalYears:4
    }
};

目標:接下來我希望不直接操作data的數據,而是用另外的方法去操作data的數據,並且當data數據發送改變時能被我寫的代碼檢測到

既然不是直接操作,那麼用戶和真實數據應該有一個 中間層,所以我把它明明為Middle

這個中間層呢,使用Object.defineProperty() 把data的數據掛載到自己實例上,可以操作它的實例間接更改data

(換個說法:在上一節age就是真實數據,而person.age實際上是真實數據的代理,我並沒有直接操作age,只不過這次數據交給data對象,更加的密集,集中保管和內部維護)

function Middle(obj){
    let keys = Object.keys(obj); //拿到所有的key
    for(let key of keys){
        //如果是對象
        if(typeof obj[key] === "object" && !(obj[key] instanceof Array)){
            this[key] = new Middle(obj[key]); //如果是對象嵌套那麼遞歸調用
            continue;
        }else{
            //過濾undefined
            if(!obj[key]){
                continue;
            }

            //設置數據代理
            Object.defineProperty(this,key,{
                get(){
                    //讀取就返回原模原樣的值
                    return obj[key];
                },
                set(newVal){
                    //賦值就修改原始數據
                    flashVirtualDom();
                    obj[key] = newVal;
                }
            })
        }
    }
}

此時,在console執行如下代碼

> let m = new Middle(data);
undefined
> m.age = 30
檢測到數據更改,準備刷新虛擬dom
30
> m.age
30

展開m,與Vue實例的_data進行對比,相差不大,當我修改其中一個數據時會調用flashVirtualDom();方法用於刷新dom,同樣還可以寫其他方法做其他操作


數據劫持

在尚矽谷的課程中,有提到”數據劫持“這一概念,對此,本篇也相應的記錄下

個人理解的數據劫持,實際上是當數據發生變動時,率先攔截變動,做出處理後,在決定是否要變動數據

或者更改變動的結果,其在我理解更類似於hook的操作,當某個函數或者變數被賦值,然後我對他進行一次攔截

攔截之後做出我想要的操作,操作做完再讓他回歸正常運行


Vue中的數據監測

在做Vue的開發中,Vue並非對任何數據都做了監測,因此我認為我作為Vue的學習者,應當去瞭解具體有哪些情況不會被監測到,從而避免日後開發的各種奇奇怪怪的問題。

對於常見數據,有兩種比較容易出錯

  • 對象
  • 數組

對象相關的數據監測問題&解決方案

假設,在初始的data裡面僅有如下數據

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./vue.js"></script>
    <title>Document</title>
</head>
<body>
    <div id="root">
        <div>姓名:{{person.name}}</div>
        <div>性別:{{person.sex}}</div>
        <div>年齡:{{person.age}}</div>
    </div>
</body>
<script>
    let vm = new Vue({
        el:"#root",
        data:{
            person:{
                name:"張三",
                sex:"男"
            }
        }
    })
</script>
</html>

這都很正常,但如果我想不修改這個代碼,在項目上線後根據後端給的數據動態的增加

假設在某處代碼上後端返回的數據有年齡,我想data的數據增加一條年齡並且展示到應該展示的位置

那麼我試圖這麼做

vm.data.age = 19; //假設後端給出的數據是19

此時頁面無任何變化

(實際操作,可以先讓頁面運行起來,然後再console上去追加一個年齡)

這個後期追加的數據在Vue中並沒有被監測,導致他沒有顯示(簡單說就是沒有get和set方法)

那我該如何解決呢?

Vue非常人性化的提供另外的方法:Vue.set( target, propertyName/index, value )

註:用vm.$set也是一樣的,詳細區別可以去翻官方文檔

在這個方法允許給某個對象添加屬性並且監測

  • target:目標對象
  • propertyName/index:屬性名稱/索引值(可用於數組)
  • value:值

所以當我接收到後端數據時,我可以用這個方法追加數據並且被Vue所監測

Vue.set(vm.person,'age',19); //假設後端給出的數據是19

此時age即可直接顯示到頁面上了


數組相關的數據監測問題&解決辦法

假設,這次問題代碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./vue.js"></script>
    <title>Document</title>
</head>
<body>
    <div id="root">
        <ul>
            <li v-for="per in friends" :key="per.id">
                {{per}}
            </li>
        </ul>
    </div>
</body>
<script>
    let vm = new Vue({
        el:"#root",
        data:{
            friends:["張三","李四","王五","趙高"]
        }
    })
</script>
</html>

現在呢,我想要修改friends第一個元素,修改為”張四“

正常做法如下:

vm.friends[0] = "張四";

但頁面數據無變化,實際數據已經更改了

這是為什麼呢?展開friends數組,發現這並沒有數組成員的get和set方法,Vue並未對數組成員做監測,因此改了之後,數據並未刷新

那麼,我該如何做呢?官網其實早就給出了答案:變更方法。 官方對以下七個方法進行了包裹(我感覺hook更好理解)

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

依次,但我通過這些方法去做數組進行”增刪改查“時,Vue會檢測到,所以我想修改第一個元素為李四可以這麼乾

vm.friends.splice(0,1,"張四")

執行完後頁面變了,說明變動被監測到了,除此之外還可以使用set方法

Vue.set(vm.friends,0,"張四");

The End

唔~(一口濁氣)

這一篇可真長啊

本篇完~~~~


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 摘要: 在開發iOS應用程式時,一個重要的任務是確保應用在不同的屏幕和設備上呈現出良好的用戶體驗。為了實現這一目標,iOS提供了尺寸類別、Auto Layout和Size Classes等強大的工具和技術。 尺寸類別是描述設備屏幕尺寸的屬性,它可以幫助開發者瞭解當前界面所在的設備環境。明確的設備尺寸... ...
  • 全新研發flutter3+dart3+photo_view跨多端仿微信App界面聊天Flutter3-Chat。 flutter3-chat基於最新跨全平臺技術flutter3+dart3+material-design+shared_preferences+easy_refresh構建的仿微信AP ...
  • 一、擴展函數 擴展函數可以方便地給現有類增加屬性和方法而不改動類地代碼。 二、原理 fun String.addTo(s: String): String{ return this + s } 反編譯: @Metadata( mv = {1, 6, 0}, k = 2, d1 = {"\u0000\ ...
  • 響應式設計旨在編寫一套代碼,在不同設備上都能有良好的表現。響應式設計有兩種思路:桌面端優先和移動端優先,它們的區別是先實現一種佈局,再使用媒體查詢設置斷點,實現不同屏幕尺寸下的佈局,逐漸過渡到另一端。即由大到小和由小到大的區別。 ...
  • 當有多條衝突的CSS規則指向同一元素,則瀏覽器會計算特異性選擇更具體的規則。如果特異性相同,則按照代碼順序,靠後的規則覆蓋前面的規則。 ...
  • 背景:自動化部署系統主要可以集成到公司內部的管理系統中去,比如公司有多個項目,移動端H5,大屏網站,門戶網站等...每次發佈或者迭代都需要前端同事打包然後在交給運維或者後端同事放到伺服器上進行部署 ,如果有一個項目多個同事合作完成 還要走git合併流程,所以我們的目標就是不讓前端進行打包,開發完成代 ...
  • Web API SpeechSynthesis是一項強大的瀏覽器功能,它允許開發者將文本轉換為語音,並通過瀏覽器播放出來。本文將深入探討SpeechSynthesis的控制介面,包括其功能、用法和一個完整的JavaScript示例。 參考資料:SpeechSynthesis - Web API 介面 ...
  • 自己常用的 TS 寫法總結,應該會一直更新。可使用 TS線上編譯 校驗 TS 語法。 基本用法 普通 const num: number = 10 const isStop: boolean = false const title: string = '常用TS總結' const curName: ...
一周排行
    -Advertisement-
    Play Games
  • 在C#中使用SQL Server實現事務的ACID(原子性、一致性、隔離性、持久性)屬性和使用資料庫鎖(悲觀鎖和樂觀鎖)時,你可以通過ADO.NET的SqlConnection和SqlTransaction類來實現。下麵是一些示例和概念說明。 實現ACID事務 ACID屬性是事務處理的四個基本特征, ...
  • 我們在《SqlSugar開發框架》中,Winform界面開發部分往往也用到了自定義的用戶控制項,對應一些特殊的界面或者常用到的一些局部界面內容,我們可以使用自定義的用戶控制項來提高界面的統一性,同時也增強了使用的便利性。如我們Winform界面中用到的分頁控制項、附件顯示內容、以及一些公司、部門、菜單的下... ...
  • 在本篇教程中,我們學習瞭如何在 Taurus.MVC WebMVC 中進行數據綁定操作。我們還學習瞭如何使用 ${屬性名稱} CMS 語法來綁定頁面上的元素與 Model 中的屬性。通過這些步驟,我們成功實現了一個簡單的數據綁定示例。 ...
  • 是在MVVM中用來傳遞消息的一種方式。它是在MVVMLight框架中提供的一個實現了IMessenger介面的類,可以用來在ViewModel之間、ViewModel和View之間傳遞消息。 Send 接受一個泛型參數,表示要發送的消息內容。 Register 方法用於註冊某個對象接收消息。 pub ...
  • 概述:在WPF中,通過EventHandler可實現基礎和高級的UI更新方式。基礎用法涉及在類中定義事件,併在UI中訂閱以執行更新操作。高級用法藉助Dispatcher類,確保在非UI線程上執行操作後,通過UI線程更新界面。這兩種方法提供了靈活而可靠的UI更新機制。 在WPF(Windows Pre ...
  • 概述:本文介紹了在C#程式開發中如何利用自定義擴展方法測量代碼執行時間。通過使用簡單的Action委托,開發者可以輕鬆獲取代碼塊的執行時間,幫助優化性能、驗證演算法效率以及監控系統性能。這種通用方法提供了一種便捷而有效的方式,有助於提高開發效率和代碼質量。 在軟體開發中,瞭解代碼執行時間是優化程式性能 ...
  • 概述:Cron表達式是一種強大的定時任務調度工具,通過配置不同欄位實現靈活的時間規定。在.NET中,Quartz庫提供了簡便的方式配置Cron表達式,實現精準的定時任務調度。這種靈活性和可擴展性使得開發者能夠根據需求輕鬆地制定和管理定時任務,例如每天備份系統日誌或其他重要操作。 Cron表達式詳解 ...
  • 概述:.NET提供多種定時器,如System.Windows.Forms.Timer適用於UI,System.Web.UI.Timer用於Web,System.Diagnostics.Timer用於性能監控,System.Threading.Timer和System.Timers.Timer用於一般 ...
  • 問題背景 有同事聯繫我說,在生產環境上,訪問不了我負責的common服務,然後我去檢查common服務的health endpoint, 沒問題,然後我問了下異常,timeout導致的System.OperationCanceledException。那大概率是客戶端的問題,會不會是埠耗盡,用ne ...
  • 前言: 在本篇 Taurus.MVC WebMVC 入門開發教程的第四篇文章中, 我們將學習如何實現數據列表的綁定,通過使用 List<Model> 來展示多個數據項。 我們將繼續使用 Taurus.Mvc 命名空間,同時探討如何在視圖中綁定並顯示一個 Model 列表。 步驟1:創建 Model ...