Reflect和Proxy

2021/07/06

Proxy

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。 ES6 原生提供 Proxy 构造函数,用来生成 Proxy 实例。var proxy = new Proxy(target, handler); Proxy 对象的所有用法,都是上面这种形式,不同的只是 handler 参数的写法。其中,

const proxy = new Proxy(
  {},
  {
    get(target, property) {
      return 35;
    },
  }
);
proxy.time; // 35
proxy.name; // 35
proxy.title; // 35

上面代码中,作为构造函数,Proxy 接受两个参数。第一个参数是所要代理的目标对象(上例是一个空对象),即如果没有 Proxy 的介入,操作原来要访问的就是这个对象;第二个参数是一个配置对象,对于每一个被代理的操作,需要提供一个对应的处理函数,该函数将拦截对应的操作。比如,上面代码中,配置对象有一个 get 方法,用来拦截对目标对象属性的访问请求。get 方法的两个参数分别是目标对象和所要访问的属性。可以看到,由于拦截函数总是返回 35,所以访问任何属性都得到 35。 注意,要使得 Proxy 起作用,必须针对Proxy实例(上例是 proxy 对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。

如果 handler 没有设置任何拦截,那就等同于直接通向原对象。

const peo = {};

const proxyP = new Proxy(peo, {});

proxyP.name = 'ck';
peo.name; // 'ck'

下面是 Proxy 支持的拦截操作一览,一共 13 种。

Reflect

Reflect 是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法与处理器对象的方法相同。与大多数全局对象不同,Reflect 没有构造函数。你不能将其与一个 new 运算符一起使用,或者将 Reflect 对象作为一个函数来调用。Reflect 的所有属性和方法都是静态的(就像 Math 对象)。

Reflect 对象提供以下静态函数,它们具有与处理器对象方法相同的名称。这些方法中的一些与 Object 上的对应方法相同。

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)
const myObject = {
  foo: 1,
  bar: 2,
  get baz() {
    return this.foo + this.bar;
  },
};

Reflect.get(myObject, 'foo'); // 1
Reflect.get(myObject, 'bar'); // 2
Reflect.get(myObject, 'baz'); // 3

demo

观察者模式:当一个对象被修改时,则会自动通知它的依赖对象

// 存放修改时触发的行为
const queuedObservers = new Set();
function observe(fn) {
  queuedObservers.add(fn);
}

// 返回一个观察对象
function observable(obj) {
  return new Proxy(obj, {
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      queuedObservers.forEach((fn) => {
        fn();
      });
      return result;
    },
  });
}

const person = observable({
  name: '张三',
  age: 20,
});

function print() {
  console.log(`${person.name}, ${person.age}`);
}

observe(print);
person.name = '李四';
person.name = 'ck';

包装原始数据来记录有关函数持续时间的计时数据

function wrapper(handler) {
  return (...args) => {
    console.time();
    try {
      return new.target
        ? Reflect.construct(handler, args)
        : Reflect.apply(handler, this, args);
    } finally {
      console.timeEnd();
    }
  };
}

// hello world
// default: 4.751ms