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));
后續再寫