零基礎入門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 微服務框架,幫助我們輕鬆構建和管理微服務應用。 本框架不僅支持 Consul 服務註 ...
  • 先看一下效果吧: 如果不會寫動畫或者懶得寫動畫,就直接交給Blend來做吧; 其實Blend操作起來很簡單,有點類似於在操作PS,我們只需要設置關鍵幀,滑鼠點來點去就可以了,Blend會自動幫我們生成我們想要的動畫效果. 第一步:要創建一個空的WPF項目 第二步:右鍵我們的項目,在最下方有一個,在B ...
  • Prism:框架介紹與安裝 什麼是Prism? Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架 Github https://github.com/PrismLibrary/Prism NuGet htt ...
  • 在WPF中,屏幕上的所有內容,都是通過畫筆(Brush)畫上去的。如按鈕的背景色,邊框,文本框的前景和形狀填充。藉助畫筆,可以繪製頁面上的所有UI對象。不同畫筆具有不同類型的輸出( 如:某些畫筆使用純色繪製區域,其他畫筆使用漸變、圖案、圖像或繪圖)。 ...
  • 前言 嗨,大家好!推薦一個基於 .NET 8 的高併發微服務電商系統,涵蓋了商品、訂單、會員、服務、財務等50多種實用功能。 項目不僅使用了 .NET 8 的最新特性,還集成了AutoFac、DotLiquid、HangFire、Nlog、Jwt、LayUIAdmin、SqlSugar、MySQL、 ...
  • 本文主要介紹攝像頭(相機)如何採集數據,用於類似攝像頭本地顯示軟體,以及流媒體數據傳輸場景如傳屏、視訊會議等。 攝像頭採集有多種方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),網上一些文章以及 ...
  • 前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
  • 背景需求: 系統需要對接到XXX官方的API,但因此官方對接以及管理都十分嚴格。而本人部門的系統中包含諸多子系統,系統間為了穩定,程式間多數固定Token+特殊驗證進行調用,且後期還要提供給其他兄弟部門系統共同調用。 原則上:每套系統都必須單獨接入到官方,但官方的接入複雜,還要官方指定機構認證的證書 ...
  • 本文介紹下電腦設備關機的情況下如何通過網路喚醒設備,之前電源S狀態 電腦Power電源狀態- 唐宋元明清2188 - 博客園 (cnblogs.com) 有介紹過遠程喚醒設備,後面這倆天瞭解多了點所以單獨加個隨筆 設備關機的情況下,使用網路喚醒的前提條件: 1. 被喚醒設備需要支持這WakeOnL ...
  • 前言 大家好,推薦一個.NET 8.0 為核心,結合前端 Vue 框架,實現了前後端完全分離的設計理念。它不僅提供了強大的基礎功能支持,如許可權管理、代碼生成器等,還通過採用主流技術和最佳實踐,顯著降低了開發難度,加快了項目交付速度。 如果你需要一個高效的開發解決方案,本框架能幫助大家輕鬆應對挑戰,實 ...