Proxy学习笔记

129 阅读10分钟

代理基础

new Proxy(target,handler)
  • target:要使用Proxy包装的对象,可以是任何类型的对象,包括原生数组,函数以及另一个代理
  • handler:一个通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理的行为 缺少了其中一个都会抛出TypeError

在代理对象上执行的任何操作实际上都会应用到目标对象,唯一可感知不同的就是操作的是代理对象

<script>
    const obj = {
      age:17
    }
    const pxy = new Proxy(obj,{});
    console.log(pxy.age,obj.age);//17 17
</script>

给目标属性赋值会反映在两个对象上,因为两个对象访问的是同一个值, 给代理属性赋值会反映在两个对象上,因为这个赋值会转移到目标对象上

Proxy.prototype是undefined,所以不能使用instanceof操作符 可以使用严格等于来区分代理和目标

const obj = {
    age:17
    }
const pxy = new Proxy(obj,{})
console.log(pxy === obj);//false

定义捕获器

使用代理的主要目的是可以定义捕获器,捕获器就是在处理程序对象中定义的基本操作的拦截器,每个处理程序对象可以包含零个或多个捕获器,每个捕获器都对应一种基本操作,可以直接或间接在代理对象上进行调用,每次在代理对象上调用这些基本操作时,代理可以在这些操作传播到目标对象之前先调用捕获器函数,从而拦截并修改相应的行为

注意,只有在代理对象上执行某些操作才会触发捕获器,在目标对象上执行这些操作仍然会产生正常的行为

<script>
    const obj = {
      age:17
    }
    const pxy = new Proxy(obj,{
      get(){
        return 18
      }
    });
    console.log(obj.age,pxy.age);//17,18
</script>

捕获器参数和反射API

所有捕获器都可以访问相应的参数,基于这些参数可以重建被捕获方法的原始行为,比如:get()捕获器会接收到目标对象,要查询的属性和代理对象三个参数,有了这些参数,就可以重建被捕获方法的原始行为

<script>
    const obj = {
      age:17
    }
    const pxy = new Proxy(obj,{
      get(mub,shux,pro){
        return mub[shux] = 19
      }
    });
    console.log(obj.age,pxy.age);//17,19
  </script>

但是通过手动重构原始行为的想法是不显示的,所以js提供了反射API(Reflect对象),可以通过调用全局Reflect对象上的同名方法进行重建

处理对象中所有可以进行捕获的方法都有相应的反射API的方法,这些方法与捕获器拦截的方法具有相同的名称和函数签名,而且也具有与被拦截方法相同的行为

可撤销代理

有时候可能需要中断代理对象与目标对象之间的联系。对于使用new Proxy()创建的普通代理来说,这种联系会在代理对象的生命周期内一直持续存在

Proxy也暴露了revocable()方法,这个方法支持撤销代理对象与目标对象的关联。撤销代理的操作是不可逆的,而且不管调用多少次,结果都是一样的,撤销之后再调用代理会抛出TypeError

撤销函数和代理对象是在实例化时同时生成的

<script>
    const obj = {
      age:17
    }
    const {proxy:pro,revoke} = Proxy.revocable(obj,{});
    console.log(pro.age);//17
    revoke()
    console.log(pro.age);//TypeError
  </script>

实用反射API

某些情况下应该优先使用反射API 在使用反射API时要记住:

  • 反射API并不限于捕获处理程序
  • 大多数反射API方法在Object类型上有对应的方法

通常,Object上的方法适用于通用程序,而反射方法用于细粒级的对象控制和操作

很多反射方法返回称作"状态标记"的布尔值,表示意图执行的操作是否成功,以下反射方法都会提供状态标记:

  • Reflect.defineProperty()

  • Reflect.preventExtensions()

  • Reflect.setPrototypeOf()

  • Reflect.set()

  • Reflect.deleteProperty() 以下反射方法提供只有通过操作符才能完成的操作:

  • Reflect.get():可以替代对象属性访问操作符

  • Reflect.set():可以替代=赋值操作符

  • Reflect.has():可以替代in操作符或with()

  • Reflect.deleteProperty():可以替代delete操作符

  • Reflect.construct():可以替代new操作符

代理另一个代理

代理可以拦截反射API的操作,而这意味着完全可以创建一个代理,通过它去代理另一个代理。这样就可以在一个目标对象之上构建多层拦截网

<script>
    const obj = {
      age:17
    }
    const first = new Proxy(obj,{
      get(x,y){
        return x.y = 16
      }
    })
    const second = new Proxy(first,{
      get(x,y){
        return x.y = 15
      }
    })

    console.log(second.age);
  </script>

代理捕获器与反射方法

代理可以捕获13种不同的基本操作。这些操作有各自不同的反射API方法、参数、关联ECMAScript操作和不变式 在代理对象上执行任何一种操作,只会有一种捕获处理程序被调用,不会存在重复捕获的情况

get()

get()捕获器在获取属性值的操作中被调用,对应的反射API方法为Reflect.get()

  1. 返回值:没有限制
  2. 拦截的操作:
    • proxy.property
    • proxy[property]
    • Object.create(proxy)[property]
    • Reflect.get(proxy, property, receiver)
  3. 捕获器处理程序参数:
    • 目标对象(target)
    • 引用的目标对象上的字符串键属性(property)
    • 代理对象或继承代理对象的对象(receiver)
  4. 捕获器不变式
    • 如果target.property不可写且不可配置,则处理程序返回的值必须与target.property匹配
    • 如果target.property不可配置且[[Get]]特性为undefined,处理程序的返回值也必须是undefined

set()

会在设置属性值的操作中被调用,对应的反射API为Reflect.set()

  1. 返回值
    • true表示设置成功
    • false表示设置失败
  2. 拦截的操作
    • proxy.property = value
    • proxy[property] = value
    • Object.create(proxy)[property] = value
    • Reflect.set(proxy, property, value, receiver)
  3. 捕获器处理程序参数
    • 目标对象
    • 引用的目标对象的字符串键属性
    • 要赋值给属性的值
    • 接收最初赋值对象
  4. 捕获器不变式
    • 如果target.property不可写且不可配置,则不能修改目标属性的值
    • 如果target.property不可配置且[[Set]]特性为undefined,则不能修改目标属性的值
    • 在严格模式下,处理程序中返回false会抛出TypeError

has()

会在in操作符中被调用,对应的反射API的方法是Reflect.has()

  1. 返回值:
    • 必须是布尔值,表示属性是否存在,返回非布尔值会被转换为布尔值
  2. 拦截的操作
    • property in proxy
    • property in Object.create(proxy)
    • with(proxy) {(property); }
    • Reflect.has(proxy, property)
  3. 捕获器处理程序参数
    • 目标对象
    • 引用的目标对象上的字符串键属性
  4. 捕获器不变式
    • 如果target.property存在且不可配置,则处理程序必须返回true
    • 如果target.property存在且目标对象不可扩展,则处理程序必须返回true

defineProperty()

会在Object.defineProperty()中被调用。对应的反射API方法为Reflect.defineProperty()

  1. 返回值
    • defineProperty()必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值
  2. 拦截的操作
    • Object.defineProperty(proxy, property, descriptor)
    • Reflect.defineProperty(proxy, property, descriptor)
  3. 捕获器处理程序参数
    • 目标对象
    • 引用的目标对象的字符串键属性
    • 包含可选的enumerable、configurable、writable、value、get和set定义的对象
  4. 捕获器不变式
    • 如果目标对象不可扩展,则无法定义属性
    • 如果目标对象有一个可配置的属性,则不能添加同名的不可配置属性
    • 如果目标对象有一个不可配置的属性,则不能添加同名的可配置属性

getOwnPropertyDescriptor()

会在Object.getOwnPropertyDescriptor()中被调用。对应的反射API方法为Reflect.getOwnPropertyDescriptor()

  1. 返回值
    • getOwnPropertyDescriptor()必须返回对象,或者在属性不存在时返回undefined
  2. 拦截的操作
    • Object.getOwnPropertyDescriptor(proxy, property)
    • Reflect.getOwnPropertyDescriptor(proxy, property)
  3. 捕获器处理程序参数
    • 目标对象
    • 引用的目标对象上的字符串键属性
  4. 捕获器不变式
    • 如果自有的target.property存在且不可配置,则处理程序必须返回一个表示该属性存在的对象
    • 如果自有的target.property存在且可配置,则处理程序必须返回表示该属性可配置的对象
    • 如果自有的target.property存在且target不可扩展,则处理程序必须返回一个表示该属性存在的对象
    • 如果target.property不存在且target不可扩展,则处理程序必须返回undefined表示该属性不存在
    • 如果target.property不存在,则处理程序不能返回表示该属性可配置的对象

deleteProperty()

会在delete操作符中被调用。对应的反射API方法为Reflect. deleteProperty()

  1. 返回值
    • deleteProperty()必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值
  2. 拦截的操作
    • delete proxy.property
    • delete proxy[property]
    • Reflect.deleteProperty(proxy, property)
  3. 捕获器处理程序参数
    • 目标对象
    • 引用目标对象上的字符串键属性
  4. 捕获器不变式
    • 如果自有的target.property存在且不可配置,则处理程序不能删除这个属性

ownKeys()

会在Object.keys()及类似方法中被调用。对应的反射API方法为Reflect. ownKeys()

  1. 返回值
    • ownKeys()必须返回包含字符串或符号的可枚举对象
  2. 拦截的操作
    • Object.getOwnPropertyNames(proxy)
    • Object.getOwnPropertySymbols(proxy)
    • Object.keys(proxy)
    • Reflect.ownKeys(proxy)
  3. 捕获器处理程序参数
    • target:目标对象
  4. 捕获器不变式
    • 返回的可枚举对象必须包含target的所有不可配置的自有属性
    • 如果target不可扩展,则返回可枚举对象必须准确地包含自有属性键

getPrototypeOf()

会在Object.getPrototypeOf()中被调用。对应的反射API方法为Reflect.getPrototypeOf()

  1. 返回值
    • getPrototypeOf()必须返回对象或null
  2. 拦截的操作
    • Object.getPrototypeOf(proxy)
    • Reflect.getPrototypeOf(proxy)
    • proxy.proto
    • Object.prototype.isPrototypeOf(proxy)
    • proxy instanceof Object
  3. 捕获器处理程序参数
    • 目标对象
  4. 捕获器不变式
    • 如果target不可扩展,则Object.getPrototypeOf(proxy)唯一有效的返回值就是Object. getPrototypeOf(target)的返回值

setPrototypeOf()

会在Object.setPrototypeOf()中被调用。对应的反射API方法为Reflect.setPrototypeOf()

  1. 返回值
    • setPrototypeOf()必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值
  2. 拦截的操作
    • Object.setPrototypeOf(proxy)
    • Reflect.setPrototypeOf(proxy)
  3. 捕获器处理程序参数
    • 目标对象
    • prototype:target的替代原型,如果是顶级原型则为null
  4. 捕获器不变式
    • 如果target不可扩展,则唯一有效的prototype参数就是Object.getPrototypeOf(target)的返回值

isExtensible()

会在Object.isExtensible()中被调用。对应的反射API方法为Reflect.isExtensible()

  1. 返回值
    • isExtensible()必须返回布尔值,表示target是否可扩展。返回非布尔值会被转型为布尔值。
  2. 拦截的操作
    • Object.isExtensible(proxy)
    • Reflect.isExtensible(proxy)
  3. 捕获器处理程序参数
    • 目标对象
  4. 捕获器不变式
    • 如果target可扩展,则处理程序必须返回true
    • 如果target不可扩展,则处理程序必须返回false

preventExtensions()

会在Object.preventExtensions()中被调用。对应的反射API方法为Reflect.preventExtensions()

  1. 返回值
    • preventExtensions()必须返回布尔值,表示target是否已经不可扩展。返回非布尔值会被转型为布尔值
  2. 拦截的操作
    • Object.preventExtensions(proxy)
    • Object.preventExtensions(proxy)
  3. 捕获器处理程序参数
    • 目标对象
  4. 捕获器不变式
    • 如果Object.isExtensible(proxy)是false,则处理程序必须返回true。

apply()

会在调用函数时中被调用。对应的反射API方法为Reflect.apply()

  1. 返回值
    • 无限制
  2. 拦截的操作
    • proxy(...argumentsList)
    • Function.prototype.apply(thisArg, argumentsList)
    • Function.prototype.call(thisArg, ...argumentsList)
    • Reflect.apply(target, thisArgument, argumentsList)
  3. 捕获器处理程序参数
    • 目标对象
    • 调用函数时this的参数
    • 调用函数时的参数列表
  4. 捕获器不变式
    • 必须是一个函数对象

construct()

会在new操作符中被调用。对应的反射API方法为Reflect.construct()

  1. 返回值
    • 必须返回一个对象
  2. 拦截的操作
    • new proxy(...argumentsList)
    • Reflect.construct(target, argumentsList, newTarget)
  3. 捕获器处理程序参数
    • 目标构造函数
    • 传给目标构造函数的参数列表
    • 最初被调用的构造函数
  4. 捕获器不变式
    • target必须可以用作构造函数

代理模式