主頁 > 企業開發 > 一探 Vue 資料回應式原理

一探 Vue 資料回應式原理

2020-09-13 01:24:55 企業開發

一探 Vue 資料回應式原理

本文寫于 2020 年 8 月 5 日

相信在很多新人第一次使用 Vue 這種框架的時候,就會被其修改資料便自動更新視圖的操作所震撼,

Vue 的檔案中也這么寫道:

Vue 最獨特的特性之一,是其非侵入性的回應式系統,資料模型僅僅是普通的 JavaScript 物件,而當你修改它們時,視圖會進行更新,

單看這句話,像我這種菜鳥程式員必然是看不懂的,我只知道,在 new Vue() 時傳入的 data 屬性一旦產生變化,那么在視圖里的變數也會隨之而變,

但這個變化是如何實作的呢?接下來讓我們,一探究竟,

1 偷偷變化的 data

我們先來新建一個變數:let data = https://www.cnblogs.com/xhyccc/p/{ msg:'hello world' }

接著我們將這個 data 傳給 Vue 的 data:

let data = https://www.cnblogs.com/xhyccc/p/{ msg:'hello world' }

/*****留空處*****/

new Vue({
  data,
  methods: {
    showData() {
      console.log(data)
    }
  }
})

這看似是非常平常的操作,但是我們在觸發 showData 的時候,會發現打出來 data 不太對勁:

msg: (...)
__ob__: Observer {value: {…}, dep: Dep, vmCount: 1}
get msg: ? reactiveGetter()
set msg: ? reactiveSetter(newVal)
__proto__: Object

它不僅多了很多沒見過的屬性,還把里面的 msg: hello world 變成了 msg: (...)

接下來我們嘗試在留空處列印出 data,即在定義完 data 之后立即將其列印,

但是很不幸,列印出來依然是上面這個不對勁的值,

可是很明顯,當我們不去 new Vue(),并且傳入 data 的時候,data 的列印結果絕對不是這樣,

所以我們可以嘗試利用 setTimeout()new Vue() 延遲 3 秒執行,

這個時候我們就會驚訝的發現:

  1. 當我們在 3s 內點開 console 的結果時,data 是普通的形式;
  2. 當我們在 3s 后點開 console 的結果時,data 又變成了奇怪的形式,

這說明就是 new Vue() 的程序中,Vue 偷偷的對 data 進行了修改!正是這個修改,讓 data 的資料,變成了回應式資料,

2 (...) 的由來

為什么好好的一個 msg 屬性會變成 (...) 呢?

這就涉及到了 ES6 中的 getter 和 setter,(如果理解 getter/setter,可跳至下一節)

一般我們如果需要計算后的值,會定義一個函式,例如:

const obj = {
  number: 5,
  double() {
    return this.number * 2;
  }
};

在使用的時候,我們寫上 obj.double(obj.number) 即可,

但是函式是需要加括號的,我太懶了,以至于括號都不想要了,

于是就有了 getter 方法:

const obj = {
  number: 5,
  get double() {
    return this.number * 2;
  }
};

const newNumber = obj.double;

這樣一來,就能夠不需要括號,就可以得到 return 的值,

setter 同理:

const obj = {
  number: 5,
  set double(value) {
    if(this.number * 2 != value;)
    this.number = value;
  }
};

obj.double = obj.number * 2;

由此我們可以看出:通過 setter,我們可以達到給賦值設限的效果,例如這里我就要求新值必須是原值的兩倍才可以,

但經常的,我們會用 getter/setter 來隱藏一個變數

比如:

const obj = {
  _number: 5,
  get number() {
    return this._number;
  },
  set number(value) {
    this._number = value;
  }
};

這個時候我們列印出 obj,就會驚訝的發現 (...) 出現了:

number: (...)
_number: 5

現在我們明白了,Vue 偷偷做的事情,就是把 data 里面的資料全變成了 getter/setter,

3 利用 Object.defineProperty() 實作代理

這個時候我們想一個問題,原來我們可以通過 obj.c = 'c'; 來定義 c 的值——即使 c 本身不在 obj 中,

但如何定義一個 getter/setter 呢?答:使用 Object.defineProperty()

Object.defineProperty() 方法會直接在一個物件上定義一個新屬性,或者修改一個物件的現有屬性,并回傳此物件,

例如我們上面寫的 obj.c = 'c';,就可以通過

const obj = {
  a: 'a',
  b: 'b'
}
Object.defineProperty(obj, 'c', {
  value: 'c'
})

Object.defineProperty() 接收三個引數:第一個是要定義屬性的物件;第二個是要定義或修改的屬性的名稱或 Symbol;第三個則是要定義或修改的屬性描述符,

在第三個引數中,可以接收多個屬性,value 代表「值」,除此之外還有 configurable, enumerable, writable, get, set 一共六個屬性,

這里我們只看 getset

之前我們說了,通過 getter/setter 我們可以把不想讓別人直接操作的資料“藏起來”,

可是本質上,我們只是在前面加了一個 _ 而已,直接訪問是可以繞過我們的 getter/setter 的!

那么我們怎么辦呢?

利用代理,這個代理不是 ES6 新增的 Proxy,而是設計模式的一種,

我們剛剛為什么可以去修改我們“藏起來”的屬性值?

因為我們知道它的名字呀!如果我不給他名字,自然別人就不可能修改了,

例如我們寫一個函式,然后把資料傳進去:

proxy({ a: 'a' })

這樣一來我們的 { a: 'a' } 就根本沒有名字了,無從改起!

接下來我們在定義 proxy 函式時,可以新建一個空物件,然后遍歷傳入的值,分別進行 Object.defineProperty()將傳入的物件的 keys 作為 getter/setter 賦給新建的空物件

最后,我們 return 這個物件即可,

let data = https://www.cnblogs.com/xhyccc/p/proxy({
  a:'a',
  b: 'b'
});

function proxy(data) {
  const obj = {};
  const keys = Object.keys(data);
  for (let i = 0; i < keys.length; i++) {
    Object.defineProperty(obj, keys[i], {
      get() {
        return data[keys[i]];
      },
      set(value) {
        if (value < 0) return;
        data[keys[i]] = value;
      }
    });
  }
  return obj;
}

這樣一來,我們一開始宣告的 data,就是我們 return 的物件了,在這個物件里,沒有原始的資料,別人無法繞過 getter/setter 進行操作!

但是往往并沒有這么簡單,如果我一定需要一個變數名呢?

const sourceData = https://www.cnblogs.com/xhyccc/p/{
  a:'a',
  b: 'b'
};

let data = https://www.cnblogs.com/xhyccc/p/proxy(sourceData);

如此一來,通過直接操作 sourceData.a,時可以直接繞過我們在 proxy 中設定的 set a 進行賦值的,這個時候我們怎么處理?

很簡單嘛,當我們遍歷傳入的資料時,我們可以對傳入的資料新增 getter/setter,此后原始的資料就會被 getter/setter 所替代

在剛剛的代碼中,我們在回圈的剛開始添加這樣一段代碼:

for(/*......*/) {
  const value = https://www.cnblogs.com/xhyccc/p/data[keys[i]];
  Object.defineProperty(data, keys[i], {
    get() {
      return value;
    },
    set(newValue) {
      if (newValue < 0) return;
      value = newValue;
    }
  });
  /*......*/
}

這是什么意思呢?

我們利用了閉包,將原始值單獨拎出來,每一次對原始屬性進行讀寫,其實都是 get 和 set 在讀取閉包時被拎出來的值,

那么不管別人是操作我們的 let data = https://www.cnblogs.com/xhyccc/p/proxy(sourceData); 的 data,還是操作 sourceData,都會被我們的 getter/setter 所攔截,

4 回到 Vue

我們剛剛寫的代碼是這樣的:

let data = https://www.cnblogs.com/xhyccc/p/proxy({
  a:'a'
})

function proxy(data) {

}

那如果我改成這樣呢:

let data = proxy({
  data: {
    a: 'a'
  }
})

function proxy({ data }) {
  // 結構賦值
}

是不是和 Vue 就非常非常像了!

const vm = new Vue({ data: {} }) 也是讓 vm 成為 data 的代理,并且就算你從外部將資料傳給 data,也會被 Vue 所捕捉,

而在每一次捕獲到你操作資料之后,就會對需要改變的 UI 進行重新渲染,

同理,Vue 對 computed 和 watch 也存在著各種偷偷的處理,

5 Vue 資料回應式的 Bug

如果我們的資料是這樣:

data: {
  obj: {
    a: 'a'
  }
}

我們在 Vue 的 template 里卻寫了 <div>{{ obj.b }}<div> 會怎樣?

Vue 對于不存在或者為 undefined 和 null 的資料是不予以顯示的,但是當我們往 obj 中新增 b 的時候,他會顯示嗎?

寫法一:

const vm = new Vue({
  data: {
    obj: {
      a: 'a'
    }
  },
  methods: {
    changeObj() {
      this.obj.b = 'b';
    }
  }
})

我們可以給一個按鈕系結 changeObj 事件,但是很遺憾,這樣并不能使視圖中的 obj.b 顯示出來,

回想一下剛剛我們對于資料的處理,是不是只遍歷了外層?這就是因為 Vue 并沒有對 b 進行監聽,他根本不知道你的 b 是如何變化的,自然也就不會去更新視圖層了,

寫法 2:

const vm = new Vue({
  data: {
    obj: {
      a: 'a'
    }
  },
  methods: {
    changeObj() {
      this.obj.a = 'a2'
      this.obj.b = 'b';
    }
  }
})

我們僅僅只是新增了一行代碼,在改變 b 之前先改變了 a,居然就讓 b 實作了更新!

這是為什么?

因為視圖更新其實是異步的,

當我們讓 a'a' 變成 'a2' 時,Vue 會監聽到這個變化,但是 Vue 并不能馬上更新視圖,因為 Vue 是使用 Object.defineProperty() 這樣的方式來監聽變化的,監聽到變化后會創建一個視圖更新任務到任務佇列里,

所以在視圖更新之前,要先把余下的代碼運行完才行,也就是會運行 b = 'b'

最后等到視圖更新的時候,由于 Vue 會去做 diff 演算法,于是 Vue 就會發現 a 和 b 都變了,自然會去更新相對應的視圖,

但是這并不是我們解決問題的辦法,寫法 2 充其量只能算是“副作用”,

Vue 其實提供了方法讓我們來新增以前沒有生命的屬性:Vue.set() 或者 this.$set()

Vue.set(this.obj, 'b', 'b'); 會代替我們進行 obj.b = 'b';,然后監聽 b 的變化,觸發視圖更新,

那陣列怎么回應呢?

每當我們往陣列里新增元素的時候,陣列就在不斷的變長,對于沒有宣告的陣列下標,很明顯 Vue 不會給予監聽呀,

比如 a: [1, 2, 3],當我新增一個元素,讓 a === [1, 2, 3, 4] 的時候,a[3] 是不會被監聽的

總不能每次 push 陣列,都要手寫剛剛說的 Vue.set 方法吧,

可實際操作中,我們發現并沒有呀,Vue 監聽了新增的資料,

這是因為 Vue 又偷偷的干了一件事兒,它把你原本的陣列方法給改了一些,

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

在 Vue 中的陣列所帶的這七個方法都不是原生的方法了,Vue 考慮到這些操作極為常用,所在中間為我們添加了監聽,

講到這里,相信大家對 Vue 的回應式原理應該有了更深的認識了,

(完)

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/18387.html

標籤:JavaScript

上一篇:遍歷陣列,物件和JSON

下一篇:svn作業中的使用,最后兩項真實環境的筆記增加

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 使用Django Rest framework搭建Blog

    在前面的Blog例子中我們使用的是GraphQL, 雖然GraphQL的使用處于上升趨勢,但是Rest API還是使用的更廣泛一些. 所以還是決定回到傳統的rest api framework上來, Django rest framework的官網上給了一個很好用的QuickStart, 我參考Qu ......

    uj5u.com 2023-04-20 08:17:54 more
  • 記錄-new Date() 我忍你很久了!

    這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 大家平時在開發的時候有沒被new Date()折磨過?就是它的諸多怪異的設定讓你每每用的時候,都可能不小心踩坑。造成程式意外出錯,卻一下子找不到問題出處,那叫一個煩透了…… 下面,我就列舉它的“四宗罪”及應用思考 可惡的四宗罪 1. Sa ......

    uj5u.com 2023-04-20 08:17:47 more
  • 使用Vue.js實作文字跑馬燈效果

    實作文字跑馬燈效果,首先用到 substring()截取 和 setInterval計時器 clearInterval()清除計時器 效果如下: 實作代碼如下: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta ......

    uj5u.com 2023-04-20 08:12:31 more
  • JavaScript 運算子

    JavaScript 運算子/運算子 在 JavaScript 中,有一些運算子可以使代碼更簡潔、易讀和高效。以下是一些常見的運算子: 1、可選鏈運算子(optional chaining operator) ?.是可選鏈運算子(optional chaining operator)。?. 可選鏈操 ......

    uj5u.com 2023-04-20 08:02:25 more
  • CSS—相對單位rem

    一、概述 rem是一個相對長度單位,它的單位長度取決于根標簽html的字體尺寸。rem即root em的意思,中文翻譯為根em。瀏覽器的文本尺寸一般默認為16px,即默認情況下: 1rem = 16px rem布局原理:根據CSS媒體查詢功能,更改根標簽的字體尺寸,實作rem單位隨螢屏尺寸的變化,如 ......

    uj5u.com 2023-04-20 08:02:21 more
  • 我的第一個NPM包:panghu-planebattle-esm(胖虎飛機大戰)使用說明

    好家伙,我的包終于開發完啦 歡迎使用胖虎的飛機大戰包!! 為你的主頁添加色彩 這是一個有趣的網頁小游戲包,使用canvas和js開發 使用ES6模塊化開發 效果圖如下: (覺得圖片太sb的可以自己改) 代碼已開源!! Git: https://gitee.com/tang-and-han-dynas ......

    uj5u.com 2023-04-20 08:01:50 more
  • 如何在 vue3 中使用 jsx/tsx?

    我們都知道,通常情況下我們使用 vue 大多都是用的 SFC(Signle File Component)單檔案組件模式,即一個組件就是一個檔案,但其實 Vue 也是支持使用 JSX 來撰寫組件的。這里不討論 SFC 和 JSX 的好壞,這個仁者見仁智者見智。本篇文章旨在帶領大家快速了解和使用 Vu ......

    uj5u.com 2023-04-20 08:01:37 more
  • 【Vue2.x原始碼系列06】計算屬性computed原理

    本章目標:計算屬性是如何實作的?計算屬性快取原理以及洋蔥模型的應用?在初始化Vue實體時,我們會給每個計算屬性都創建一個對應watcher,我們稱之為計算屬性watcher ......

    uj5u.com 2023-04-20 08:01:31 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:01:10 more
  • http1.1與http2.0

    一、http是什么 通俗來講,http就是計算機通過網路進行通信的規則,是一個基于請求與回應,無狀態的,應用層協議。常用于TCP/IP協議傳輸資料。目前任何終端之間任何一種通信方式都必須按Http協議進行,否則無法連接。tcp(三次握手,四次揮手)。 請求與回應:客戶端請求、服務端回應資料。 無狀態 ......

    uj5u.com 2023-04-20 08:00:32 more