Skip to content

Proxy & Reflect

早期痛点

在 ES6 以前,如何知道一个对象被访问了或者被赋值了呢?
Object.defineProperty 通过下方案例,我们可以通过 closure 的方式,劫持对象中的 getter / setter

js
var obj = {
  name: "ice",
  age: 24,
};

var objName = obj.name;

Object.defineProperty(obj, "name", {
  get() {
    console.log("get 触发");
    return objName;
  },
  set(newValue) {
    console.log("set 触发");
    objName = newValue;
  },
});

obj.name = "panda";
console.log(obj.name);

/**
 * set 触发
 * get 触发
 * panda
 */

如果需要劫持多个 key / value,可以通过以下方式,同时 Vue2 中,实现响应式原理也是通过该 API

js
var obj = {
  name: "ice",
  age: 24,
};

Object.keys(obj).forEach((key) => {
  var value = obj[key];
  Object.defineProperty(obj, key, {
    get() {
      return value;
    },
    set(newValue) {
      value = newValue;
    },
  });
});

obj.name = "panda";
console.log(obj.name);

虽然可以监听对象的 getter / setter,但是其他的操作比如:删除,新增 它是无能为力的,并且这个 API 设计的初衷并不是为了监听对象的。

Proxy 代理

通过 Proxy 可以创建出一个代理对象,对代理对象进行一些列操作会被捕获器捕获

js
var obj = {
  name: "ice",
  age: 24,
};

const proxyed = new Proxy(obj, {
  get(target, key, receiver) {
    console.log("get 触发");
    return target[key];
  },
  set(target, key, value, receiver) {
    console.log("set"); // set
    target[key] = value;
  },
  deleteProperty(target, key) {
    console.log("deleteProperty"); // deleteProperty
    return delete target[key];
  },
});

proxyed.address = "Hangzhou";
delete proxyed.address;

Object.defineProperty 不同的是,它有更强大的监听能力,有 13 个捕获器并且也能监听对象的新增,删除等。另外要特殊说明的是其中的 target === obj,而 receiver === proxyed Vue3 源码中实现响应式则是通过 Proxy 的方式

Reflect 反射

Reflect 提供了许多操作对象的方法,与 Object 中操作的对象方法类似,而 Object 作为构造函数把这些静态方法放在它身上并不合适,所以有了 Reflect。
大部分反射的 API 在 Object 中都有对应的方法

js
const obj = {
  name: "ice",
};
Reflect.get(obj, "name"); // ice
Reflect.set(obj, "name", "panda"); // true
Reflect.has(obj, "name"); // true
// ...

了解了 Reflect 方法,我们来改造下原有的 Proxy

js
const obj = {
  name: "ice",
  age: 24,
};

const proxyed = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key);
  },
  set(target, key, value, receiver) {
    console.log("set"); // set
    return Reflect.set(target, key, value);
  },
  deleteProperty(target, key) {
    console.log("del"); // del
    return Reflect.deleteProperty(target, key);
  },
});

proxyed.address = "Hangzhou";
delete proxyed.address;

另外有一个参数 receiver 参数,我们一直都没有用到,那它是用来干嘛的?

  • 当源对象使用了 getter/setter 时,可以通过 receiver 改变其内部的 this 指向
js
const obj = {
  name: "ice",
  age: 24,
  get address() {
    console.log(this === proxyed); // true
    return "Hangzhou";
  },
};

const proxyed = new Proxy(obj, {
  get(target, key, receiver) {
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log("set"); // set
    return Reflect.set(target, key, value, receiver);
  },
  deleteProperty(target, key) {
    console.log("del"); // del
    return Reflect.deleteProperty(target, key);
  },
});

转载请注明出处,违者必究