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));

后续再写

咫尺远近却无法靠近的那个你,才敢让你发觉你并不孤寂
原文地址:https://www.cnblogs.com/tcz1018/p/14736145.html