Skip to content

Proxy ​

2023-08-10 14:47:57

proxy 是 ES6+ 的语法,用于对数据劫持,从而实现基本操作的拦截和自定义,如属性查找、赋值、枚举、函数调用等,Vue3 的响应式原理就是使用 proxy 实现的

基本使用 ​

先来创建一个 Proxy 对象,要求传入两个参数,第一个参是被代理的对象,第二个参是捕获器

ts
const target = { a: 1 }
const handler = {}
const p = new Proxy(target, handler)

console.log(p) // { a: 1 }

当我们通过 proxy 对象来改变对象属性时,target 对象也会跟着改变

ts
p.a = 2
console.log(p) // { a: 2 }
console.log(target) // { a: 2 }

同样的改变 target 对象属性时 proxy 对象也会同步改变

ts
target.a = 3
console.log(p) // { a: 3 }
console.log(target) // { a: 3 }

捕获器 ​

在 handler 中定义一个捕获器,会在 get 被触发时调用

ts
const target = { a: 1 }
const handler = {
  get: () => {
    return 'get is captured'
  }
}
const p = new Proxy(target, handler)

console.log(p.a) // get is captured
console.log(target.a) // 1

捕获器可以访问到对应的参数,get() 捕获器可以接收到目标对象、要查询的属性和代理对象三个参数

ts
const target = { a: 1 }
const handler = {
  get: (trapTarget, property, receiver) => {
    console.log('trapTarget:', trapTarget)
    console.log('property:', property)
    console.log('receiver:', receiver)
  }
}
const p = new Proxy(target, handler)

console.log(p.a)
// 打印结果
// trapTarget: { a: 1 }
// property: a
// receiver: { a: 1 }

有了这些参数就可以自定义一些捕获方法

撤销 ​

Proxy 暴露了 revocable()方法,这个方法支持撤销代理对象与目标对象的关联

撤销代理的操作是不可逆的。而且,撤销函数(revoke())是幂等的,调用多少次的结果都一样。撤销代理之后 再调用代理会抛出 TypeError

ts
const target = {
  a: 1
}
const handler = {
  get() {
    return 2
  }
}
const { proxy, revoke } = Proxy.revocable(target, handler)
console.log(proxy.a) // 2
console.log(target.a) // 1
revoke()
console.log(proxy.a) // TypeError

代理模式 ​

跟踪属性访问 ​

ts
const person = {
  name: 'Jake'
}
const proxy = new Proxy(person, {
  get(target, property, receiver) {
    console.log(`Getting ${property}`)
    return Reflect.get(...arguments)
  },
  set(target, property, value, receiver) {
    console.log(`Setting ${property}=${value}`)
    return Reflect.set(...arguments)
  }
})
proxy.name // Getting name
proxy.age = 27 // Setting age=27

属性验证 ​

因为所有赋值操作都会触发 set()捕获器,所以可以根据所赋的值决定是允许还是拒绝赋值:

ts
const target = {
  onlyNumbersGoHere: 0
}
const proxy = new Proxy(target, {
  set(target, property, value) {
    if (typeof value !== 'number') {
      return false
    } else {
      return Reflect.set(...arguments)
    }
  }
})
proxy.onlyNumbersGoHere = 1
console.log(proxy.onlyNumbersGoHere) // 1
proxy.onlyNumbersGoHere = '2'
console.log(proxy.onlyNumbersGoHere) // 1

Released under the MIT License.