你是怎麼理解ES6中Proxy的?使用場景?

来源:https://www.cnblogs.com/smileZAZ/p/18071312
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、介紹 定義: 用於定義基本操作的自定義行為 本質: 修改的是程式預設形為,就形同於在編程語言層面上做修改,屬於元編程(meta programming) 元編程(Metaprogramming,又譯超編程,是指某類電腦程式的編寫,這 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

一、介紹

定義: 用於定義基本操作的自定義行為

本質: 修改的是程式預設形為,就形同於在編程語言層面上做修改,屬於元編程(meta programming)

元編程(Metaprogramming,又譯超編程,是指某類電腦程式的編寫,這類電腦程式編寫或者操縱其它程式(或者自身)作為它們的數據,或者在運行時完成部分本應在編譯時完成的工作

一段代碼來理解

#!/bin/bash
# metaprogram
echo '#!/bin/bash' >program
for ((I=1; I<=1024; I++)) do
    echo "echo $I" >>program
done
chmod +x program

這段程式每執行一次能幫我們生成一個名為program的文件,文件內容為1024行echo,如果我們手動來寫1024行代碼,效率顯然低效

  • 元編程優點:與手工編寫全部代碼相比,程式員可以獲得更高的工作效率,或者給與程式更大的靈活度去處理新的情形而無需重新編譯

Proxy 亦是如此,用於創建一個對象的代理,從而實現基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數調用等)

二、用法

Proxy為 構造函數,用來生成 Proxy實例

var proxy = new Proxy(target, handler)

參數

target表示所要攔截的目標對象(任何類型的對象,包括原生數組,函數,甚至另一個代理))

handler通常以函數作為屬性的對象,各屬性中的函數分別定義了在執行各種操作時代理 p 的行為

handler解析

關於handler攔截屬性,有如下:

  • get(target,propKey,receiver):攔截對象屬性的讀取
  • set(target,propKey,value,receiver):攔截對象屬性的設置
  • has(target,propKey):攔截propKey in proxy的操作,返回一個布爾值
  • deleteProperty(target,propKey):攔截delete proxy[propKey]的操作,返回一個布爾值
  • ownKeys(target):攔截Object.keys(proxy)for...in等迴圈,返回一個數組
  • getOwnPropertyDescriptor(target, propKey):攔截Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象
  • defineProperty(target, propKey, propDesc):攔截Object.defineProperty(proxy, propKey, propDesc),返回一個布爾值
  • preventExtensions(target):攔截Object.preventExtensions(proxy),返回一個布爾值
  • getPrototypeOf(target):攔截Object.getPrototypeOf(proxy),返回一個對象
  • isExtensible(target):攔截Object.isExtensible(proxy),返回一個布爾值
  • setPrototypeOf(target, proto):攔截Object.setPrototypeOf(proxy, proto),返回一個布爾值
  • apply(target, object, args):攔截 Proxy 實例作為函數調用的操作
  • construct(target, args):攔截 Proxy 實例作為構造函數調用的操作

Reflect

若需要在Proxy內部調用對象的預設行為,建議使用Reflect,其是ES6中操作對象而提供的新 API

基本特點:

  • 只要Proxy對象具有的代理方法,Reflect對象全部具有,以靜態方法的形式存在
  • 修改某些Object方法的返回結果,讓其變得更合理(定義不存在屬性行為的時候不報錯而是返回false
  • Object操作都變成函數行為

下麵我們介紹proxy幾種用法:

get()

get接受三個參數,依次為目標對象、屬性名和 proxy 實例本身,最後一個參數可選

var person = {
  name: "張三"
};

var proxy = new Proxy(person, {
  get: function(target, propKey) {
    return Reflect.get(target,propKey)
  }
});

proxy.name // "張三"

get能夠對數組增刪改查進行攔截,下麵是試下你數組讀取負數的索引

function createArray(...elements) {
  let handler = {
    get(target, propKey, receiver) {
      let index = Number(propKey);
      if (index < 0) {
        propKey = String(target.length + index);
      }
      return Reflect.get(target, propKey, receiver);
    }
  };

  let target = [];
  target.push(...elements);
  return new Proxy(target, handler);
}

let arr = createArray('a', 'b', 'c');
arr[-1] // c

註意:如果一個屬性不可配置(configurable)且不可寫(writable),則 Proxy 不能修改該屬性,否則會報錯

const target = Object.defineProperties({}, {
  foo: {
    value: 123,
    writable: false,
    configurable: false
  },
});

const handler = {
  get(target, propKey) {
    return 'abc';
  }
};

const proxy = new Proxy(target, handler);

proxy.foo
// TypeError: Invariant check failed

set()

set方法用來攔截某個屬性的賦值操作,可以接受四個參數,依次為目標對象、屬性名、屬性值和 Proxy 實例本身

假定Person對象有一個age屬性,該屬性應該是一個不大於 200 的整數,那麼可以使用Proxy保證age的屬性值符合要求

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // 對於滿足條件的 age 屬性以及其他屬性,直接保存
    obj[prop] = value;
  }
};

let person = new Proxy({}, validator);

person.age = 100;

person.age // 100
person.age = 'young' // 報錯
person.age = 300 // 報錯

如果目標對象自身的某個屬性,不可寫且不可配置,那麼set方法將不起作用

const obj = {};
Object.defineProperty(obj, 'foo', {
  value: 'bar',
  writable: false,
});

const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = 'baz';
  }
};

const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"

註意,嚴格模式下,set代理如果沒有返回true,就會報錯

'use strict';
const handler = {
  set: function(obj, prop, value, receiver) {
    obj[prop] = receiver;
    // 無論有沒有下麵這一行,都會報錯
    return false;
  }
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'

deleteProperty()

deleteProperty方法用於攔截delete操作,如果這個方法拋出錯誤或者返回false,當前屬性就無法被delete命令刪除

var handler = {
  deleteProperty (target, key) {
    invariant(key, 'delete');
    Reflect.deleteProperty(target,key)
    return true;
  }
};
function invariant (key, action) {
  if (key[0] === '_') {
    throw new Error(`無法刪除私有屬性`);
  }
}

var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: 無法刪除私有屬性

註意,目標對象自身的不可配置(configurable)的屬性,不能被deleteProperty方法刪除,否則報錯

取消代理

Proxy.revocable(target, handler);

三、使用場景

Proxy其功能非常類似於設計模式中的代理模式,常用功能如下:

  • 攔截和監視外部對對象的訪問
  • 降低函數或類的複雜度
  • 在複雜操作前對操作進行校驗或對所需資源進行管理

使用 Proxy 保障數據類型的準確性

let numericDataStore = { count: 0, amount: 1234, total: 14 };
numericDataStore = new Proxy(numericDataStore, {
    set(target, key, value, proxy) {
        if (typeof value !== 'number') {
            throw Error("屬性只能是number類型");
        }
        return Reflect.set(target, key, value, proxy);
    }
});

numericDataStore.count = "foo"
// Error: 屬性只能是number類型

numericDataStore.count = 333
// 賦值成功

聲明瞭一個私有的 apiKey,便於 api 這個對象內部的方法調用,但不希望從外部也能夠訪問 api._apiKey

let api = {
    _apiKey: '123abc456def',
    getUsers: function(){ },
    getUser: function(userId){ },
    setUser: function(userId, config){ }
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
    get(target, key, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} 不可訪問.`);
        } return Reflect.get(target, key, proxy);
    },
    set(target, key, value, proxy) {
        if(RESTRICTED.indexOf(key) > -1) {
            throw Error(`${key} 不可修改`);
        } return Reflect.get(target, key, value, proxy);
    }
});

console.log(api._apiKey)
api._apiKey = '987654321'
// 上述都拋出錯誤

還能通過使用Proxy實現觀察者模式

觀察者模式(Observer mode)指的是函數自動觀察數據對象,一旦對象有變化,函數就會自動執行

observable函數返回一個原始對象的 Proxy 代理,攔截賦值操作,觸發充當觀察者的各個函數

const queuedObservers = new Set();

const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});

function set(target, key, value, receiver) {
  const result = Reflect.set(target, key, value, receiver);
  queuedObservers.forEach(observer => observer());
  return result;
}

觀察者函數都放進Set集合,當修改obj的值,在會set函數中攔截,自動執行Set所有的觀察者

參考文獻

  • https://es6.ruanyifeng.com/#docs/proxy
  • https://vue3js.cn/es6

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

-Advertisement-
Play Games
更多相關文章
  • 正處於企業指標建設過程中的你,是否經常遇到這樣的問題: • 各個部門獨立建設信息系統,由此產生的指標定義和計算方式各異,導致管理層無法快速準確地掌握整體業務運行狀況 • 缺乏對指標的統一管理和規範,產生重覆的指標計算工作,導致數據計算資源被過度消耗,增加運維成本和數據處理壓力 • 不知道指標體系建設 ...
  • GreatSQL里也能用上RocksDB引擎 1. 前言 RocksDB 是基於Facebook 開源的一種支持事務的、高度可壓縮、高性能的MyRocks存儲引擎,特別適用於高度壓縮和大容量的數據。以下是一些關鍵特點: 高性能: LSM 樹結構使得RocksDB在寫入密集型負載下表現卓越。它能夠處理 ...
  • 原文: Android 桌面小組件使用-Stars-One的雜貨小窩 藉助公司上的幾個項目,算是學習了Android桌面小組件的用法,記下踩坑記錄 基本步驟 1.創建小組件佈局 這裡需要註意的事,小組件佈局里不能使用自定義View,只能使用原生的組件,比如說LinearLayout,TextView ...
  • XML 是一種用於存儲和傳輸數據的與軟體和硬體無關的工具。 什麼是XML? XML代表eXtensible Markup Language(可擴展標記語言)。XML是一種與HTML非常相似的標記語言。XML被設計用於存儲和傳輸數據。XML被設計成具有自我描述性。XML不執行任何操作,也許有點難理解, ...
  • HTML特性: 1.空白摺疊現象 1.文字間折為一個空格 <p>hello world!</p> 2.標簽內壁空白忽略 <p> hello world! </p> 2.轉義字元 <p>小於號&lt;</p> <p>大於號&gt;</p> <p>空格&nbsp;</p> <p>版權號&copy;</p ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 一、介紹 Generator 函數是 ES6 提供的一種非同步編程解決方案,語法行為與傳統函數完全不同 回顧下上文提到的解決非同步的手段: 回調函數 promise 那麼,上文我們提到promsie已經是一種比較流行的解決非同步方案,那麼為什麼 ...
  • 涉及的技術棧 vue3 vite bootstrap5 背景 在用bootstrap5的時候遇到一個問題,就是offcanvas在nav上的時候居然會有兩個背景BackDrop,關閉之後頁面上還有一個backdrop留在那 bootstrap5文檔裡面提供了幾個Method可以控制Offcanvas ...
  • 本文介紹三種使用純 CSS 實現星級評分的方式。每種都值得細品一番~ 五角星取自 Element Plus 的 svg 資源 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" style=""> <path fill="c ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...