整理下困扰很久的Proxy与Reflect

128 阅读3分钟

前言

这两个对象都是ES6新增的对象,之前零零散散用过一些,今天做下简单的总结。

Proxy

概述

个人理解是对目标的对象做一层拦截操作,凡事要操作目标对象(读、写等)都需要经过这层代理进行拦截,拦截过后才能访问到目标对象。Vue3的数据绑定也是使用Proxy实现的(区别于Vue2Object.defineProperty)

基本语法

var proxy = new Proxy(target, handler);

其中 target 表示要拦截的目标对象,handler是拦截操作的定义,也是一个Javascript对象。

handler 列表

-   get(target, propKey, receiver) :拦截对象属性的读取,比如`proxy.foo``proxy['foo']`。
-   set(target, propKey, value, receiver) :拦截对象属性的设置,比如`proxy.foo = v``proxy['foo'] = v`,返回一个布尔值。
-   has(target, propKey) :拦截`propKey in proxy`的操作,返回一个布尔值。
-   deleteProperty(target, propKey) :拦截`delete proxy[propKey]`的操作,返回一个布尔值。
-   ownKeys(target) :拦截`Object.getOwnPropertyNames(proxy)``Object.getOwnPropertySymbols(proxy)``Object.keys(proxy)``for...in`循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而`Object.keys()`的返回结果仅包括目标对象自身的可遍历属性。
-   getOwnPropertyDescriptor(target, propKey) :拦截`Object.getOwnPropertyDescriptor(proxy, propKey)`,返回属性的描述对象。
-   defineProperty(target, propKey, propDesc) :拦截`Object.defineProperty(proxy, propKey, propDesc)``Object.defineProperties(proxy, propDescs)`,返回一个布尔值。
-   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 实例作为函数调用的操作,比如`proxy(...args)``proxy.call(object, ...args)``proxy.apply(...)`。
-   construct(target, args) :拦截 Proxy 实例作为构造函数调用的操作,比如`new proxy(...args)`

示例

const 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');
      }
    }

    // The default behavior to store the value
    obj[prop] = value;

    // 表示成功
    return true;
  }
};

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

person.age = 100;

console.log(person.age);
// 100

person.age = 'young';
// 抛出异常: Uncaught TypeError: The age is not an integer

person.age = 300;
// 抛出异常: Uncaught RangeError: The age seems invalid

好处

第一次使用这个对象是由于一次前端bug的修复: 之前遇到一个奇怪的问题,在用户做了某些操作之后,一个全局对象被修改了,导致了一些奇奇怪怪的问题。 所以使用了Proxy进行对这个全局对象进行拦截,当set的拦截器中打个断点,通过看 Call Stack 找到了修改对象的代码位置。 后面也陆陆续续用这个方法定位过问题,自认为还是挺好用的。(但是对 Proxy的使用也只是停留在这么基本的使用,如果后续有更好的使用姿势,继续更新在这里)。

兼容性

image.png

Reflect

概述

将部分 Object 上的方法,放到 Reflect上做静态方法使用。(说实话,get不到 Reflect的用处有多大,至少跟ES6其他语法比较,可能只是想规范下 Object上的方法。)

基本语法

image.png

Relect.xxx(target, ...args)

静态方法列表

-   Reflect.apply(target, thisArg, args)
-   Reflect.construct(target, args)
-   Reflect.get(target, name, receiver)
-   Reflect.set(target, name, value, receiver)
-   Reflect.defineProperty(target, name, desc)
-   Reflect.deleteProperty(target, name)
-   Reflect.has(target, name)
-   Reflect.ownKeys(target)
-   Reflect.isExtensible(target)
-   Reflect.preventExtensions(target)
-   Reflect.getOwnPropertyDescriptor(target, name)
-   Reflect.getPrototypeOf(target)
-   Reflect.setPrototypeOf(target, prototype)

大部分方法都能在 Object上找到,也和Proxy拦截器一一对应。

示例

const duck = {
  name: 'Maurice',
  color: 'white',
  greeting: function() {
    console.log(`Quaaaack! My name is ${this.name}`);
  }
}
Reflect.get(duck, 'name')
// Maurice

Reflect.has(duck, 'color');
// true
Reflect.has(duck, 'haircut');
// false

好处

待发掘。。。。。。

兼容性

image.png

参考