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));
后续再写