從 defineProperty 到 Proxy

来源:https://www.cnblogs.com/wisewrong/archive/2020/04/14/12630627.html
-Advertisement-
Play Games

眾所周知,Vue 2.x 的數據綁定是通過 defineProperty。而在 Vue 3.x 的設計中,數據綁定是通過 Proxy 實現的,這兩者到底有何異同? 一、definePropety defineProperty 是 Object 的一個方法,可以在對象上新增或編輯某個屬性,可編輯的內容 ...


眾所周知,Vue 2.x 的數據綁定是通過 defineProperty。而在 Vue 3.x 的設計中,數據綁定是通過 Proxy 實現的,這兩者到底有何異同?

 

一、definePropety

defineProperty 是 Object 的一個方法,可以在對象上新增或編輯某個屬性,可編輯的內容除了屬性值 value 之外,還有該屬性的描述信息

Object.defineProperty(obj, prop, descriptor)

該方法接收三個參數,分別是目標對象 obj,被編輯的屬性名 prop,以及該屬性的描述 descriptor

需要註意的是,只能在 Object 構造器對象使用該方法,實例化的 object 類型是沒有該方法的

 

 

1. 基礎描述符

  • configurable當該鍵值為 true 時,該屬性的描述符才能夠被改變,同時該屬性也能從對應的對象上被刪除。預設為 false。

當該描述符為 false 的時候,其它的描述符一旦定義,就無法再更改,且該屬性無法被 delete 刪除

 

  • enumerable當該鍵值為 true 時,該屬性才會出現在對象的枚舉屬性中。預設為 false。

當 enumerable 為 false 時,Objcet.keys() 和 for...in 都無法獲取到被定義的屬性

但 Reflect.ownKeys() 可以...

  

2. 數據描述符

  • value屬性值。可以是任何有效的 JavaScript 值 (數值,對象,函數等)。預設為 undefined。
  • writable當該鍵值為 true 時,屬性的值(即 value)才能被賦值運算符改變。 預設為 false。

 

3. 存取描述符

  • get:該屬性的 getter 函數,訪問該屬性時候會調用該函數,其返回值會被用作 value,預設為 undefined。

該函數沒有入參,但是可以使用 this 對象,只是這個 this 不一定是源對象 obj

 

  • set: 該屬性的 setter 函數,當屬性值被修改時,會調用此函數,預設為 undefined。

該方法接受一個參數,即被賦予的新值,同時會傳入賦值時的 this 對象

 

⚠️註意:數據描述符和存取描述符不可同時存在!

  

4. Vue 2.x 響應式原理

在 Vue 2.x 中其實就是在觀察者模式中使用上面提到的 get 和 set 實現的數據綁定

首先實現依賴收集和 Watcher

// 通過 Dep 解耦屬性的依賴和更新操作
class Dep {
  constructor() {
    this.subs = []
  }
  // 添加依賴
  addSub(sub) {
    this.subs.push(sub)
  }
  // 更新
  notify() {
    this.subs.forEach(sub => {
      sub.update()
    })
  }
}
// 全局屬性,通過該屬性配置 Watcher
Dep.target = null

class Watcher {
  constructor(obj, key, up) {
    // 手動觸發 getter 以添加監聽
    Dep.target = this
    this.up = up
    this.obj = obj
    this.key = key
    this.value = obj[key]
    // 完成依賴添加後重置 target
    Dep.target = null
  }
  update() {
    // 獲得新值
    this.value = this.obj[this.key]
    // 調用 update 方法更新 Dom
    this.up(this.value)
  }
}

然後通過 defineProperty 來實現響應

function observe(obj) {
  if (!obj || typeof obj !== 'object') {
    return
  }
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}

function defineReactive(obj, key, val) {
  // 遞歸子屬性
  observe(val)
  let dp = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 將 Watcher 添加到訂閱
      if (Dep.target) {
        dp.addSub(Dep.target)
      }
      return val
    },
    set(newVal) {
      val = newVal
      // 執行 watcher 的 update 方法
      dp.notify()
    }
  })
}

完成之後,通過 observe 遍歷對象,然後實例化 Watcher,手動觸發一次 getter 完成數據綁定

const data = { name: '' }
observe(data)
function update(value) {
  document.body.innerHTML = `<div>${value}</div>`
}
// 模擬解析到 `{{name}}` 觸發的操作
new Watcher(data, 'name', update)
data.name = 'Wise.Wrong'

這部分代碼參考自掘金小冊《前端面試之道》

 

二、Proxy

以 Object.defineProperty() 實現的響應式有兩個問題:

1. 給對象新增屬性並不會更新 DOM;

2. 以索引的方式修改數組也不會觸發 DOM 的更新。

最終 Vue 是通過重寫函數的方式解決了這兩個問題,但對於數組的數據綁定依然有瑕疵

而這些問題,對於 Proxy 來說都不是問題

 

1. 簡介

const p = new Proxy(target, handler)

這裡的目標對象 target 可以是任何類型的對象,包括原生數組,函數,甚至另一個 Proxy

而對應的處理器對象 handler 包含很多的 trap 方法,這些 trap 方法會在 Proxy 對象執行對應操作時觸發

下麵會介紹幾個常用的方法

getPrototypeOf() Object.getPrototypeOf 方法對應的鉤子函數
setPrototypeOf() Object.setPrototypeOf 方法對應的鉤子函數
defineProperty() Object.defineProperty 方法對應的鉤子函數
has() in 操作符對應的鉤子函數
deleteProperty() delete 操作符對應的鉤子函數
apply() 函數被調用時的鉤子函數
construct() new 操作符對應的鉤子函數
get() 屬性讀取操作的鉤子函數
set() 屬性被修改時的鉤子函數

鉤子函數會在對 Proxy 對象執行相應操作的時候觸發

 

2. 鉤子函數

以 set 和 get 為例

function update(value = 'wise.wrong') {
  console.log('update');
  document.body.innerHTML = value;
};

const data = ['who', 'am', 'i'];

const subject = new Proxy(data, {
  get: function(obj, prop) {
    return obj[prop];
  },
  set: function(obj, prop, value) {
    update(value);
    obj[prop] = value;
  }
});

上面的目標的對象是一個數組,然後實例化 Proxy 的時候添加了 set 的鉤子函數

當 Proxy 對象 subject 被修改的時候,會執行 update 方法

基於這些鉤子函數,就可以參考上面 Object.defineProperty() 的思路實現數據綁定了,而且還不會有上面的遺留問題

 

3. 和 defineProperty 的區別

defineProperty 需要針對具體的 key 設置 getter 和 setter

Object.defineProperty(obj, prop, descriptor)

以至於 Vue 2.x 在初始化的時候,需要遞歸遍歷對象的子屬性,挨個兒掛載 setter

這也導致了無法直接通過 defineProperty 實現在對象中新增屬性時更新 DOM

但 Proxy 是針對整個對象的代理,不會關心具體的 key

而且 Proxy 的目標對象並沒有類型限制,除了 Object 之外,還天然支持 Array、Function 的代理

此外 Proxy 還不僅僅支持 getter 和 setter,上面提到的鉤子函數 ,在特定的場景下會發揮出應有的作用

所以 Proxy 比 Object.defineProperty()  的層次更高,畢竟 defineProperty 只是一個方法,而 Proxy 是一個可實例化的類

 


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

-Advertisement-
Play Games
更多相關文章
  • 用Moor做TODO app: * 基本使用: 依賴添加, 資料庫和表的建立, 對錶的基本操作. * 問題解決: 插入數據註意類型; 多個表的文件組織. * 常用功能: 外鍵和join, 資料庫升級, 條件查詢. ...
  • 引言 在我們學習編程之初,就學習過變數的賦值操作,同時也學習了將一個變數的值賦值給另外一個變數。對於交換兩個變數的值,很多童鞋都有解決方案。然鵝,對於面試官提出的不藉助第三變數來交換兩個變數的值,你能想到幾種解決方案呢? 如果你只知道一種方案,請你認真看下去... 如果你知道兩種方案,那麼你可以來了 ...
  • 前幾篇都是長篇大論,一次看完的確有些費盡,今天簡單些,分享一個開發中使用attr() 的技巧,可能大家都沒有這樣使用過。它配合ES6標準中模板字元串模塊使用。簡單看下模板字元串它的使用: // 傳統的 JavaScript 語言,輸出模板通常是這樣寫的(下麵使用了 jQuery 的方法)。 $('# ...
  • 首先是安裝 在index.js中引入樣式 跟著官網點組件 import React,{Component} from 'react'; import { Button } from 'antd'; class Counter extends Component{ render(){ console. ...
  • 生命周期函數指的是組件在某一時刻會自動執行的函數 constructor可以看成一個類的普通生命周期函數,但不是react獨有的生命周期函數 render() 是數據發生變化時會自動執行的函數,因此屬於react的生命周期函數 mounting只在第一次渲染時會執行 import React,{Co ...
  • 目錄 為什麼分析asap asap概述 asap源碼解析—Node版 參考 1.為什麼分析asap 在之前的文章 "async和await是如何實現非同步編程?" 中的 “淺談Promise如何實現非同步執行” 小節,提到了 Promise 非同步執行是通過 "asap" 這個庫來實現的。所以為了進一步深 ...
  • 3-1什麼是數組?變數用來存儲數據,一個變數只能存儲一個內容,如果要存儲多個數據怎麼辦?此時就需要用到數組,數組是一個值的集合,每個值都有一個索引號,從0開始,每個索引都有一個相應的值,根據需要添加更多數值。 1 <script type="text/javascript"> 2 var myarr ...
  • 括弧功能未實現,後續更 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <tit ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...