es6新增API Proxy

311 阅读2分钟

Proxy是什么

proxy用于创建一个对象的代理,并可以监听该代理对象的相关操作。比如:访问该代理对象的某个属性,会触发get方法,给代理对象的某个属性赋值,会触发set方法。 Proxy接受两个参数,一个是源对象,一个是捕捉器,如下:

const personInfo = {
  name: '张三',
  age: 18
};

const proxy = new Proxy(personInfo, {

  get(target, key) {
    console.log('2222222', key)  // name(proxy.name代表访问了代理对象的name属性,会触发get方法)
    return target[key];
  },
  set(target, key, value) {
    target[key] = value;  
  }
});

console.log(proxy.name); // 张三

上面代码proxy是personInfo的代理对象,当我们访问proxy的name属性时,会触发get方法

const personInfo = {
  name: '张三',
  age: 18
};

const proxy = new Proxy(personInfo, {

  get(target, key) {
    console.log('2222222', key)  // name(proxy.name代表访问了代理对象的name属性,会触发get方法)
    return target[key];
  },
  set(target, key, value) {
    console.log(key, value); // name  李四
    target[key] = value;  
  }
});

proxy.name = '李四';

console.log(proxy.name, personInfo) // 李四 李四

上面代码给proxy的name属性赋值,会触发set方法,从而改变源对象的值。其实代理的对象proxy某种意义上来讲就是源对象的拷贝,所以改变target的name属性,proxy的name属性也跟着改变了。但是这会有一个问题,我们改变或者访问的是proxy的值,意味着其实我们想要操作的对象是proxy,而上面代码操作的却是源对象target。这样在一些情境下会产生bug,如下:

const personInfo = {
  name: '张三',
  age: 18,
  get value() {
    console.log(this) // 这时this指的是源对象personInfo,而不是proxy,因为在proxy.value触发get方法时,访问的是target(源对象)这显然是不准确的
    return this.name
  }
};

const proxy = new Proxy(personInfo, {

  get(target, key) {
    console.log(key) // 只打印一次key,值为value
    return target[key]
  },
  set(target, key, value) {
    target[key] = value;  
  }
});
console.log(proxy.value)

以上代码,在proxy.value的时候,其实是要触发两次get方法的,因为this.name也需要触发get方法的,但是因为在第一次触发get的时候,操作的的target源对象,所以this.name不会触发get方法,所以只答应了一次。正确写法如下:

// 源对象

const personInfo = {
  name: '张三',
  age: 18,
  get value() {
    console.log(this) // 这时this指的是代理对象proxy
    return this.name
  }
};

const proxy = new Proxy(personInfo, {

  get(target, key, receiver) { // receiver:这里表示代理对象proxy
    
    console.log(key); // 打印两次,第一次为value,第二次为name

    return Reflect.get(target, key, receiver); // Reflect表示反射
  },
  set(target, key, value) {
    const result = Reflect.set(target, key, value, receiver);
    
    return result;
  }
});
console.log(proxy.value)

以上代码,proxy.value后会触发两个get方法,因为this.name的this指向proxy,访问proxy的name属性又会触发get方法。

总结

proxy可以代理整个对象,相比于Object.defineProperty,对对象属性的增加,删除等操作都会被捕捉到,使用上也更加灵活,方便。