零基礎入門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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...