零基礎入門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
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...