Proxy 代理的使用和介紹


Proxy 代理的使用和介紹

簡介: Proxy是在ES2015就有語法,但是自己一直都沒有用過,但是覺得這個東西就跟promise一樣,是個很好的東西。所以整理一下,后續拓展。
使用的目的就是在一些相關的操作上面進行代理攔截,比如訪問數據之前的處理,進行攔截操作

Vue3 已經用 Proxy 代替了 Object.defineProperty 實現響應式。
mobx 也從 5.x 版本開始使用 Proxy 進行代理。

實現的基本結構:

/**
 * target: 表示要代理的目標,可以是object, array, function類型
 * handler: 是一個對象,可以編寫各種代理的方法
 */
const proxy = new Proxy(target, handler);

舉例子:
訪問-操作-對象的時候,進行攔截處理在進行返回

const person = {
  name: 'smallTanks',
  age: 20,
};
const personProxy = new Proxy(person, {
  get(target, key, receiver) {
    console.log(`get value by ${key}`);
    return target[key];
  },
  set(target, key, value) {
    console.log(`set ${key}, old value ${target[key]} to ${value}`);
    target[key] = value;
  },
});
//讀取:
console.log(person.name)
//輸出--- smallTanks
console.log(personProxy.name)
//輸出--- 
//get value by name
//smallTanks

//修改:
personProxy.name = 'bigTanks';
// 輸出--- set name, old value wenzi to bigTanks

並且通過 personProxy 設置數據時,代理的原結構里的數據也會發生變化。打印 person,字段 name 的值 變成bigTanks:

所以本質上代理修改就是修改像原型鏈的類似的東西,在他修改之前去做了一些處理,建議一定要去自己試一試。

Proxy的第二個參數:

在這里插入圖片描述
handelr官方文檔
我現在的理解就是類似於原型鏈的操作 就跟那個 Object.defineProperty 是的

Proxy 的第 2 個參數 handler 除了可以設置 get 和 set 方法外,這些是我參考別人的:

1. get(target, propKey, receiver):攔截對象屬性的讀取,比如 proxy.foo 和 proxy['foo']。

2. set(target, propKey, value, receiver):攔截對象屬性的設置,比如 proxy.foo = v 或 proxy['foo'] = v,返回一個布爾值。

3. has(target, propKey):攔截 propKey in proxy 的操作,返回一個布爾值。

4. deleteProperty(target, propKey):攔截 delete proxy[propKey]的操作,返回一個布爾值。

5. ownKeys(target):攔截 Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in 循環,返回一個數組。該方法返回目標對象所有自身的屬性的屬性名,而 Object.keys()的返回結果僅包括目標對象自身的可遍歷屬性。

6. getOwnPropertyDescriptor(target, propKey):攔截 Object.getOwnPropertyDescriptor(proxy, propKey),返回屬性的描述對象。

7. defineProperty(target, propKey, propDesc):攔截 Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一個布爾值。

8. preventExtensions(target):攔截 Object.preventExtensions(proxy),返回一個布爾值。

9. getPrototypeOf(target):攔截 Object.getPrototypeOf(proxy),返回一個對象。

10. isExtensible(target):攔截 Object.isExtensible(proxy),返回一個布爾值。

11. setPrototypeOf(target, proto):攔截 Object.setPrototypeOf(proxy, proto),返回一個布爾值。如果目標對象是函數,那么還有兩種額外操作可以攔截。

12. apply(target, object, args):攔截 Proxy 實例作為函數調用的操作,比如 proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。

13. construct(target, args):攔截 Proxy 實例作為構造函數調用的操作,比如 new proxy(...args)。

寫一個刪除攔截的:

const person = {
  name: 'smallTanks',
  age: 20,
};
const personProxy = new Proxy(person, {
  // 忽略get和set方法,與上面一樣
  // ...
  deleteProperty(target, key, receiver) {
    console.log(`delete key ${key}`);
    delete target[key];
  },
});

delete personProxy['age'];
// 輸出--- delete key age

拓展:Reflect

在別人的案例上面會發現有很多使用 Reflect 簡單介紹一下:
下一篇寫這個

Reflect是ES6為了操作對象而新增的API, 為什么要添加Reflect對象呢?它這樣設計的目的是為了什么?

1)將Object對象的一些明顯屬於語言內部的方法(比如Object.defineProperty),放到Reflect對象上,那么以后我們就可以從Reflect對象上可以拿到語言內部的方法。

2)在使用對象的 Object.defineProperty(obj, name, {})時,如果出現異常的話,會拋出一個錯誤,需要使用try catch去捕獲,但是使用 Reflect.defineProperty(obj, name, desc) 則會返回false。

所以規范起來就是:

const personProxy = new Proxy(person, {
  get(target, key, receiver) {
    console.log(`get value by ${key}`);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log(`set ${key}, old value ${target[key]} to ${value}`);
    return Reflect.set(target, key, value, receiver);
  },
  deleteProperty(target, key, receiver) {
    console.log(`delete key ${key}`);
    return Reflect.deleteProperty(target, key, receiver);
  },
});

代理數組的操作:

vue在劫持數組數據的時候 是重新寫了原型鏈上的幾個方法 但是在proxy上可以直接使用修改
至於原因我還沒有搞明白,反正就是下面的代理數組操作的當做參考就可以。

const arr = [1, 2, 3, 4];
const arrProxy = new Proxy(arr, {
  get(target, key, receiver) {
    console.log('arrProxy.get', target, key);
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log('arrProxy.set', target, key, value);
    return Reflect.set(target, key, value, receiver);
  },
  deleteProperty(target, key) {
    console.log('arrProxy.deleteProperty', target, key);
    return Reflect.deleteProperty(target, key);
  },
});


arrProxy[2] = 22; // arrProxy.set (4) [1, 2, 3, 4] 2 22
arrProxy[3]; // arrProxy.get (4) [1, 2, 22, 4] 3
delete arrProxy[2]; // arrProxy.deleteProperty (4) [1, 2, 22, 4] 2
arrProxy.push(5); // push操作比較復雜,這里進行了多個get()和set()操作
arrProxy.length; // arrProxy.get (5) [1, 2, empty, 4, 5] length

代理函數:

前邊都是在代理數據 現在看些代理函數:

const getSum = (...args) => {
  if (!args.every((item) => typeof item === 'number')) {
    throw new TypeError('參數應當均為number類型');
  }
  return args.reduce((sum, item) => sum + item, 0);
};
const fnProxy = new Proxy(getSum, {
  /**
   * @params {Fuction} target 代理的對象
   * @params {any} ctx 執行的上下文
   * @params {any} args 參數
   */
  apply(target, ctx, args) {
    console.log('ctx', ctx);
    console.log(`execute fn ${getSum.name}, args: ${args}`);
    return Reflect.apply(target, ctx, args);
  },
});

代理函數的相應操作,將進行輸入的值進行篩選操作

// 10, ctx為undefined, log: execute fn getSum, args: 1,2,3,4
fnProxy(1, 2, 3, 4);

// ctx為undefined, Uncaught TypeError: 參數應當均為number類型
fnProxy(1, 2, 3, '4');

// 10, ctx為window, log: execute fn getSum, args: 1,2,3,4
fnProxy.apply(window, [1, 2, 3, 4]);

// 6, ctx為window, log: execute fn getSum, args: 1,2,3
fnProxy.call(window, 1, 2, 3);

// 6, ctx為person, log: execute fn getSum, args: 1,2,3
fnProxy.apply(person, [1, 2, 3]);

應用的場景:案例

防抖的函數應用:

const throttleByProxy = (fn, rate) => {
  let lastTime = 0;
  return new Proxy(fn, {
    apply(target, ctx, args) {
      const now = Date.now();
      if (now - lastTime > rate) {
        lastTime = now;
        return Reflect.apply(target, ctx, args);
      }
    },
  });
};

const logTimeStamp = () => console.log(Date.now());
window.addEventListener('scroll', throttleByProxy(logTimeStamp, 300));

后續再寫


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM