Proxy-Reflect
Published on Feb 22, 2023, with 10 view(s) and 0 comment(s)

一,监听对象的操作

1,使用defineProperty监听对象操作

const obj = {
  name: "sun",
  age: 20,
};
Object.keys(obj).forEach((key) => {
  let value = obj[key];
  Object.defineProperty(obj, key, {
    get: function () {
      console.log(`获取了${key}属性`);
      return value;
    },
    set: function (newValue) {
      console.log(`设置了${key}属性`);
      value = newValue;
    },
  });
});
// console.log(obj.name); //获取了name属性  sun
// console.log(obj.age); //获取了age属性  20

obj.name = "lebron"; //设置了name属性
obj.age = 37; //设置了age属性

console.log(obj.name); //获取了name属性  lebron
console.log(obj.age); //获取了age属性  37

问题

  • 1,Object.defineProperty设计的初衷不是监听截止对象中所有属性的,在定义某些属性的时候初衷只是定义普通的属性,但是后面强行被添加数据属性描述符。

  • 2,Object.defineProperty属性无法监听删除属性、添加属性等操作。

2,Proxy

在ES6中,新增了一个Proxy类,这个类从名字就可以看出来,是用于帮助我们创建一个代理的:

  • 也就是说,如果我们希望监听一个对象的相关操作,那么我们可以先创建一个代理对象(Proxy对象);

  • 之后对该对象的所有操作,都通过代理对象来完成,代理对象可以监听我们想要对原对象进行哪些操作;

const obj = {
  name: "sun",
  age: 20,
};

const objProxy = new Proxy(obj, {
  get: function (target, key) {
    console.log(`获取属性 ${key}`, target);
    return target[key];
  },
  set: function (target, key, newValue) {
    console.log(`设置属性 ${key}`, target);
    target[key] = newValue;
  },
});

console.log(objProxy.name);
console.log(objProxy.age);

objProxy.name = "lebron";
objProxy.age = 37;
// 获取属性 name { name: "sun", age: 20 }
// sun
// 获取属性 age { name: "sun", age: 20 }
// 20
// 设置属性 name { name: "sun", age: 20 }
// 设置属性 age { name: "lebron", age: 20 }

Proxy的set和get捕获器如果我们想要侦听某些具体的操作,那么就可以在handler中添加对应的捕捉器(Trap) set和get分别对应的是函数类型; set函数有四个参数:

  • target:目标对象(侦听的对象);

  • property:将被设置的属性key;

  • value:新属性值;

- receiver:调用的代理对象;

get函数有三个参数:

  • target:目标对象(侦听的对象);

  • property:被获取的属性key;

  • receiver:调用的代理对象;

Proxy所有捕获器:13个

  • handler.getPrototypeOf():Object.getPrototypeOf 方法的捕捉器。

  • handler.setPrototypeOf():Object.setPrototypeOf 方法的捕捉器。

  • handler.isExtensible():Object.isExtensible 方法的捕捉器。

  • handler.preventExtensions():Object.preventExtensions 方法的捕捉器。

  • handler.getOwnPropertyDescriptor():Object.getOwnPropertyDescriptor 方法的捕捉器。

  • handler.defineProperty():Object.defineProperty 方法的捕捉器。

  • handler.ownKeys():Object.getOwnPropertyNames 方法和Object.getOwnPropertySymbols 方法的捕捉器。

  • handler.has():in 操作符的捕捉器

  • handler.get():属性读取操作的捕捉器

  • handler.set():属性设置操作的捕捉器

  • handler.deleteProperty():delete 操作符的捕捉器

  • handler.apply():函数调用操作的捕捉器

  • handler.construct():new 操作符的捕捉器

Proxy的construct和applyconstruct和apply是应用于函数对象的

function foo() {
  console.log("foo函数被调用了", this, arguments);
  return "foo";
}
const fooProxy = new Proxy(foo, {
  apply: function (target, thisArg, otherArgs) {
    console.log("函数的apply侦听");
    return target.apply(thisArg, otherArgs);
  },
  construct(target, argArray, newTarget) {
    console.log(target, argArray, newTarget);
    return new target();
  },
});

3,Reflect的作用

Reflect也是ES6新增的一个API,它是一个对象,字面的意思是反射作用它主要提供了很多操作JavaScript对象的方法,有点像Object中操作对象的方法;

  • 比如Reflect.getPrototypeOf(target)类似于Object.getPrototypeOf();

  • 比如Reflect.defineProperty(target, propertyKey, attributes)类似于Object.defineProperty() ;

如果我们有Object可以做这些操作,那么为什么还需要有Reflect这样的新增对象呢? 这是因为在早期的ECMA规范中没有考虑到这种对对象本身的操作如何设计会更加规范,所以将这些API放到了Object上面;但是Object作为一个构造函数,这些操作实际上放到它身上并不合适;另外还包含一些类似于in、delete操作符,让JS看起来是会有一些奇怪的;

所以在ES6中新增了Reflect,让我们这些操作都集中到了Reflect对象上;

Reflect的常见方法和Proxy是一一对应的,也是13个

Reflect的使用

const obj = {
  name: "sun",
  age: 20,
};

const objProxy = new Proxy(obj, {
  get: function (target, key) {
    console.log(`获取属性 ${key}`, target);
    // 因为proxy的目的就是不要在源对象上面修改属性,而target【key】本质仍然是,所以可以使用Reflect
    // 区别:Reflect下方法返回的是一个Boolean类型的值,可以用作判断设置是否成功,假设如果对象提前使用了freeze方法,是修改不了的。
    // return target[key];
    return Reflect.get(target, key);
  },
  set: function (target, key, newValue) {
    console.log(`设置属性 ${key}`, target);
    // target[key] = newValue;
    Reflect.set(target, key, newValue);
  },
  has: function (target, key) {
    console.log(`执行检测属性 ${key}是否存在`, target);
    return Reflect.has(target, key);
  },
  deleteProperty: function (target, key) {
    console.log(`执行删除${key}属性`);
    Reflect.deleteProperty(target, key);
  },
});

console.log(objProxy.name);
console.log(objProxy.age);

objProxy.name = "lebron";
objProxy.age = 37;

console.log("name" in objProxy);

delete objProxy.age;

console.log(obj);
// 获取属性 name { name: "sun", age: 20 }
// sun
// 获取属性 age { name: "sun", age: 20 }
// 20

// 设置属性 name { name: "sun", age: 20 }
// 设置属性 age { name: "lebron", age: 20 }

// 执行检测属性 name是否存在 { name: "lebron", age: 37 }
// true

// 执行删除age属性

// { name: "lebron" }

Reflect的construct

4,Receiver的作用

我们发现在使用getter、setter的时候有一个receiver的参数,如果我们的源对象(obj)有setter、getter的访问器属性,那么可以通过receiver来改变里面的this;

const obj = {
  _name: "sun",
  get name() {
    return this._name;
  },
  set name(newValue) {
    this._name = newValue;
  },
};

// 原来写法
// const objProxy = new Proxy(obj, {
//   get(target, key) {
//     console.log("执行了get捕获器");
//     return Reflect.get(target, key);
//   },
// });
// console.log(objProxy.name); // 执行了get捕获器  sun

// 该访问方法存在的问题:
// 执行objproxy中的get捕获器=>返回reflect.get(obj,key)=>执行obj中的get方法返回this._name
// 还是直接操作的obj._name,当需要对obj的访问做一些拦截的时候不好实现。
// 这时候可以通过receiver来改变obj中getter中this的指向。指向objProxy

// receiver
const objProxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log("执行了get捕获器");
    return Reflect.get(target, key, receiver);
  },
});
console.log(objProxy.name); // 执行了get捕获器  执行了get捕获器  sun