Proxy

64 阅读4分钟

Proxy 用于修改某些操作的默认行为,等同于在语言层面上做出修改,所以属于一种「元编程」,即对编程语言进行编程

Proxy 可以理解成在目标对象前假设一个「拦截层」,外界对该对象的访问都必须先通过这层拦截,因此提供了一种机制可以对外界的访问进行过滤和改写

ES6 原生提供了 Proxy 构造函数,用于生成 Proxy 实例

var proxy = new Proxy(target, handler)

Proxy 支持的所有拦截操作

  • get(target, propKey, receiver):拦截对象属性的读取
  • set(target, propKey, value, receiver):拦截对象属性的设置
  • has(target, propKey):拦截 propKey in proxy 的操作,返回布尔值
  • deleteProperty(target, propKey):拦截 delete proxy[propKey] 操作,返回布尔值
  • ownKeys(target):拦截 Object.getOwnPropertyNames(proxy) 、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy) ,返回一个数组
  • getOwnPropertyDescriptor(target, propKey):拦截 Object.getOwnPropertyDescriptor(proxy, propKey) ,返回属性的描述对象
  • defineProperty(target, propKey, propDesc):拦截 Object.defineProperty(proxy, propKey, propDesc)、Object.definProperties(proxy, propDescs),返回布尔值
  • preventExtensions(target):拦截 Object.preventExtensions(proxy),返回布尔值
  • getPrototypeOf(target):拦截 Object.getPrototypeOf(proxy),返回对象
  • isExtensible(target):拦截 Object.isExtensible(proxy),返回布尔值
  • setPrototypeOf(target, proto):拦截 Object.setPrototypeOf(proxy, proto),返回布尔值
  • apply(target, object, args):拦截 Proxy 实例,并将其作为函数调用的操作
  • constructor(target, args):拦截 Proxy 实例作为构造函数调用的操作

get()

get 方法用于拦截某个属性的读取操作

如果一个属性不可配置或不可写,则该属性不能被代理,通过 Proxy 对象访问该属性将会报错

set()

set 方法用于拦截某个属性的赋值操作

如果目标对象自身的某个属性不可写也不可配置,那么 set 不得改变这个属性的值,只能返回同样的值,否则报错

apply()

apply 方法拦截函数的调用、call 和 apply 操作

apply 方法接受 3 个参数

  • 目标对象 target
  • 目标对象的上下文 this
  • 目标对象的参数数组 args

每次执行 proxy 函数就会被 apply 方法拦截

has()

has 方法用来拦截 HasProperty 操作,即判断对象是否具有某个属性时,这个方法会生效,典型的操作就是 in 运算符

如果原对象不可配置或者禁止扩展,那么这时 has 拦截会报错

construct()

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

  • target 目标对象
  • args,构造函数的参数对象

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

deleteProperty()

deleteProperty 方法用于拦截 delete 操作,如果这个方法抛出错误或者返回 false,当前属性就无法被 delete 命令删除

目标对象自身的不可配置属性不能被 deleteProperty 方法删除,否则会报错

defineProperty()

defineProperty 方法拦截 Object.defineProperty 操作

如果目标对象不可扩展,则 definProperty 不能增加目标对象中不存在的属性,否则会报错

如果目标对象的某个属性不可写或不可配置,则 defineProperty 方法不得改变这两个设置

getOwnPropertyDescriptor()

getOwnPropertyDescriptor 拦截 Object.getOwnPropertyDescriptor(),返回一个属性描述对象或者 undefined

getPrototypeOf()

getPrototypeOf 用来拦截获取对象原型,主要拦截以下操作

  • Object.prototype.__proto__
  • Object.prototype.isPrototypeOf()
  • Object.getPrototypeOf()
  • Reflect.getPrototypeOf()
  • instanceof

getPrototypeOf 方法的返回值必须是对象或者 null,否则会报错

如果目标对象不可扩展,getPrototypeOf 方法必须返回目标对象的原型对象

isExtensible()

isExtensible 拦截 Object.isExtensible 操作,只能返回布尔值,否则返回值会自动转为布尔值

返回值必须和目标对象的 isExtensible 属性保持一致,否则会报错

ownKeys()

ownKeys 拦截对象自身属性的读取操作,具体拦截方法

  • Object.getOwnPropertyNames()
  • Object.getOwnPropertySymbols()
  • Object.keys()

使用 Object.keys() 时,有三类属性会被 ownKeys 方法自动过滤,不会返回

  • 目标对象上不存在的属性
  • 属性名为 Symbol 值
  • 不可遍历的属性

ownKeys 返回的数组成员只能是字符串或者 Symbol 值,如果有其它类型的值,或者返回的根本不是数组,会报错

如果目标对象自身包含不可配置的属性,则该属性必须被 ownKeys 返回,否则会报错

如果目标对象是不可扩展的,这时 ownKeys 返回的数组中必须包含原对象的所有属性,且不能包含多余的属性,否则会报错

preventExtensions()

preventExtensions 拦截 Object.preventExtensions(),该方法必须返回一个布尔值,否则会被自动转为布尔值

只要目标对象不可扩展时,proxy.preventExtensions 才能返回 true,否则会报错

setPrototypeOf()

setPrototypeOf 拦截 Object.setPrototypeOf 方法,该方法只能返回布尔值,否则会自动转为布尔值

如果目标对象不可扩展,setPrototypeOf 方法不得改变目标对象的原型

Proxy.revocable()

Proxy.revocable 方法返回一个可取消的 Proxy 实例

该方法返回一个对象,其 proxy 属性是 Proxy 实例,revoke 属性是一个函数,可以取消 Proxy 实例

this 问题

Proxy 可以代理针对目标对象的访问,但是它不是目标对象的透明代理,即不做任何拦截的情况下,也无法保证和目标对象行为一致

  • 原因就是在 Proxy 代理的情况下,目标对象内部的 this 关键字会指向 Proxy 代理

可以通过 this 绑定原始对象的方式解决