Proxy

169 阅读2分钟

Proxy

语法

const proxy = new Proxy(target,handler)

1、target要使用Proxy包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个代理) 2、handler:一个通常以函数作为属性的对象,用来定制拦截行为

const origin = {}
const obj = new Proxy(origin,{
  get:function(target,propKey,receiver) {
    return '10'
  }
});

obj.a            //'10'
obj.b            //'10'
origin.a         //undefined
origin.b         //undefined

上述代码我们给一个空对象的get架设了一层代理,所有get操作都会直接返回我们定制的数字10,需要注意的是,代理只会对proxy对象生效,如上方的origin就没有任何效果。

Handler对象常用的方法

  • handler.set() 属性设置操作的捕捉器
let person = {
  name:'张三',
  age:18
}
const p = new Proxy(person,{
  //第一个参数是代理的对象,第二个参数是读取的属性名
  //读取P的某个属性时调用
  get(target,propName) {
    console.log('有人读取了属性',target,propName)
  },
  //修改p的某个属性或者给p追加某个属性时调用
  set(target,propName,value) {
    console.log(`有人修改了${propName}属性`)
  }
})

//触发get
console.log(p.age)   //有人读取了属性 {name: '张三', age: 18} age
//触发set
p.sex='男'           //'男' 有人修改了sex属性
  • handler.get() 属性读取操作的捕捉器

    接受三个参数get(target,propKey,?receiver)

    1、target 目标对象 2、propKey 属性名 3、receiver Proxy实例本身

     const person = {
       like:'vuejs'
     }
     const obj = new Proxy(person,{
       get:function(target,propKey) {
         if(propKey in target) {
           return target[propKey]
         } else {
           throw new ReferenceError('Prop name\''+propKey + '\' does not exist.')
         }
       }
     })
     obj.like    // vuejs
     obj.test    // Uncaught ReferenceError: Prop name'test' does not exist
    

    上述代码在读取代理目标的值时,如果有值则直接返回,没有值就抛出一个自定义的错误

    注:1、如果要访问的目标属性是不可写以及不可配置的,则返回的值必须与该目标属性的值相同 2、如果要访问的目标属性没有配置访问方法,即get方法是undefined的,则返回值必须为undefined

    例:

    const obj = {};
    Object.defineProperty(obj,'a',{
      configurable:false,
      enumrable:false,
      value:10,
      writeable:false
    })
    const p = new Proxy(obj,{
     get:function(target,prop) {
       return 20;
     }
    })
    p.a   //'get' on proxy: property 'a' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '10' but got '20')
    
  • handler.has() in操作符的捕捉器

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

let person = {
  name:'张三',
  age:18
}
const p = new Proxy(person,{
  //删除P的某个属性时调用
  deleteProperty(target,propName) {
    console.log(`有人删除了${propName}属性`)
  }
})
  • handler.ownKeys() Object.getOwnPropertyNames方法和Object.getOwnPropertySymbols方法的捕捉器
  • handler.apply() 函数调用操作的捕捉器
  • handler.construct() new操作符的捕捉器

可撤销的Proxy

proxy有一个唯一的静态方法,Proxy.revocable(target,handler)

Proxy.revocable()方法可以用来创建一个可撤销的代理对象

该方法的返回值是一个对象,其结构为:{"proxy":proxy,"revoke":revoke}

  • proxy表示新生成的代理对象本身,和用一般方式new Proxy(target,handler)创建的代理对象没什么不同,只是它可以被撤销掉。
  • revoke撤销方法,调用的时候不需要加任何参数就可以撤销掉和它一起生成的那个代理对象。

该方法常用于对目标对象的访问,如下

const target = {name:'vuejs'}
const {proxy,revoke} = Proxy.revocable(target,handler)
proxy.name         // vuejs
revoke()           //取值完成对proxy进行封闭,撤销代理
proxy.name         //TypeError:Revoked

proxy对比Object.defineProperty

Object.defineProperty只能遍历对象属性进行劫持

function observe(obj) {
  if(typeof obj !== 'object' || ibj == null) {
    return
  }
  Object.keys(obj).forEach(key => {
    defineReactive(obj,key,obj[key])
  })
}

proxy直接可以劫持整个对象,并返回一个新对象,可以只操作新的对象达到响应式目的

function reactive(obj) {
  if(typeof obj !== 'object' && obj !== null) {
    return obj
  }
  //proxy相当于在对象外层进行拦截
  const observed = new Proxy(obj,{
    get(target,key,receiver) {
      const res = Reflect.get(target,key,receiver)
      console.log(`获取${key}:${res}`)
      return res
    },
    set(target,key,value,receiver) {
      const res = Reflect.set(target,key,value,receiver)
      console.log(`设置${key}:${value}`)
      return res
    },
    deleteProperty(target,key) {
      const res = Reflect.deleteProperty(target,key)
      console.log(`删除${key}:${value}`)
      return res
    }
  })
  return observed
}

proxy可以直接监听数组的变化(push/shift/splice)

const obj = [1,2,3]
const proxtObj = reactive(obj)
obj.push(4)
console.log(obj)    //[1,2,3,4]