# 11. Proxy应用

1.Proxy应用

要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(例中是空对象)进行操作。如果handler没有设置任何拦截,那就等同于直接通向原对象。

let proxy = new Proxy({}, {
  get:function (target,property) {
    return 35
  }
})
console.log(proxy.time) // 35

一个技巧是将 Proxy对象,设置到object.proxy属性,从而可以在object对象上调用。

var obj = {proxy: new Proxy(target, handler)}

Proxy 实例也可以作为其他对象的原型对象。

2.Proxy的实例方法

  • get()get用于拦截某个属性的读取操作。get方法可以继承
let person = {name:'zhangsan'}
let proxyone = new Proxy(person, {
  get:(target,property) => {
    if (property in target) {
      return target[property]
    }else{
      throw new Error(`Property ${property} does not exit`)
    }
  }
})
console.log(proxyone.name) // zhangsna
console.log(proxyone.age) // Property age does not exit

TIP

判断某个属性是否在对象中,用 property in target

一个属性不可配置(configurable)和不可写(writable),则该属性不能被代理,通过 Proxy对象访问该属性会报错。

const tgt = Object.defineProperties({}, {
  foo:{
    value:123,
    writable:false,
    configurable:false
  }
})
const handlerone = {
  get:(target,property)=>{return 'abc'}
}
const unwritable = new Proxy(tgt,handlerone)
console.log(unwritable.foo) // roperty 'foo' is a read-only and non-configurable data property...
  • set():set方法用来拦截某个属性的赋值操作。

假定Person对象有一个age属性,该属性应该是一个不大于200的整数,那么可以使用Proxy保证age的属性值符合要求。

let validator = {
  set:(obj,property,value) => {
    if (property === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('age is not an integer')
      }
      if (value>200) {
        throw new RangeError('the age seems invalid')
      }
    }
  }
}
let person = new Proxy({},validator)
person.age = 300 // RangeError

对象上面设置内部属性,属性名的第一个字符使用下划线开头,表示这些属性不应该被外部使用。

  • apply()apply方法拦截函数的调用call和apply操作。
// 拦截函数调用
let targetApp = () => {return 'i am the origin target'}
let handlerApp = {
  apply:()=>{ return 'i am the proxy'}
}
let p = new Proxy(targetApp, handlerApp)
console.log(p()) // i am the proxy
  • has()has方法用来拦截HasProperty操作,判断对象是否具有某属性时,这个方法会生效。典型的操作就是in运算符,如果原对象不可配置或者禁止扩展,这时has拦截会报错
let handlerHas = {
  has:(target,key)=>{
    if (key[0]==='_') return false
    return key in target
  }
}
let targetHas = {_prop:'_prop',prop:'prop'}
console.log('_prop' in targetHas) //false

has方法拦截的是HasProperty操作,而不是HasOwnProperty操作,即has方法不判断属性是对象自身的属性,还是继承的属性。另外,for...in循环也用到了in运算符,但是has拦截对for...in循环不生效。

  • construct()

construct方法用于拦截new命令,construct方法可以接受两个参数。

target: 目标对象

args:构建函数的参数对象

construct方法返回的必须是一个对象,否则会报错。

let pco = new Proxy(function(){}, {
  construct:(target,arglist) => {
    // return 1 // 'construct' on proxy:trap returned non-object
    return {1:'haha'}
  }
})
// 拦截new
console.log(new pco()) // {1:'haha'}
  • deleteProperty()deleteProperty方法用于拦截delete操作,目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错。

  • defineProperty():defineProperty方法拦截了Object.defineProperty操作。

  • getPrototypeOf():getPrototypeOf方法主要用来拦截获取对象原型。

  • isExtensible():isExtensible方法拦截Object.isExtensible操作。

  • ownKeys():ownKeys方法用来拦截对象自身属性的读取操作。

  • preventExtensions():preventExtensions方法拦截Object.preventExtensions()。

  • setPrototypeOf():setPrototypeOf方法主要用来拦截Object.setPrototypeOf方法。

  • getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(),返回属性的描述对象。

3.Proxy.revocable():Proxy.revocable方法返回一个可取消的 Proxy 实例。

WARNING

Proxy.revocable的使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。

4.this 问题

Proxy 代理的情况下,目标对象内部的this关键字会指向 Proxy 代理。有些原生对象的内部属性,只有通过正确的this才能拿到,需要this绑定原始对象才可以解决这个问题。

const targetDate = new Date('2015-01-01')
const handlerDate = {
  get:(target,prop)=>{
    if (prop === 'getDate') {
      return target.getDate.bind(targetDate)
    }
    return Reflect.get(target, prop)
  }
}
const proxyDate = new Proxy(targetDate,handlerDate)
console.log(proxyDate.getDate()) // 1