JavaScript -- 代理和反射

1,430 阅读7分钟

代理基础

代理是es6为开发者提供的可以拦截住针对对象的一些底层操作,并且额外在这基础上面添加一些其他的行为。 它相当于一个看门的,拦住来客(底层操作)问话(开发者逻辑)。行就开门进去,不行就打一顿丢出去

创建代理

const handler = {}
const target = {
  id: 'target'
}
const proxy = new Proxy(target, handler)

代理是由Proxy构造函数创建出来的一个代理对象,构造函数接收两个参数

  • target 目标对象(要操作的对象)
  • handler 处理程序对象(拦截后触发的捕获器api的集合对象)

目标对象可以直接调用,我们也可以通过代理对象进行调用。最终的操作结果也会反映到目标对象里面

const handler = {}
const target = {
  id: 'target'
}
const proxy = new Proxy(target, handler)
proxy.id = 'proxy'
// 修改代理对象的属性值,也会反映到目标对象
console.log(target) // { id: "proxy" }
console.log(proxy) // { id: 'proxy' }
// 再目标对象里面操作,同样也会反映到代理对象
// 因为引用的是同一个对象
traget.name = 'yu'
console.log(target) // { id: "proxy", name: 'yu' }
console.log(proxy) // { id: "proxy", name: 'yu' }

注:Proxy构造函数的prototype是undefined

捕获器

代理之所以能够针对底层操作进行拦截并且能够进行额外的操作,就是通过捕获器来实现的。捕获器是定义再处理程序对象里面的,一个处理程序对象可以定义多个捕获器方法。每个捕获器都对应着一个基本操作。

  • 比如获取对象属性的操作,对应着get捕获器方法
const handler = {
  get(target, property, proxy) {
  	console.log('触发了get方法')
    return target[property]
  }
}
const target = {
  id: 'target'
}
const proxy = new Proxy(target, handler)
proxy.id // '触发了get方法'

这些捕获器只在代理对象触发,直接使用目标对象是不会触发的

捕获器方法都有对应的参数,通过这些参数可以重新构建新的行为逻辑。比如get方法就会有三个参数

  • target 目标对象
  • property 要查询的属性
  • proxy 代理对象

一个代理里面包含着13种捕获器方法,这些捕获器方法可以重新构造新的行为。但是也要遵守一个就是要有这个操作的原始行为,但是有些原始行为十分复制,我们手动如法炮制可能很麻烦。所以es6给开发者提供了反射API,他给我们提供了基本的原始行为不需要我们来写。

反射(Reflect)

反射对象是一个有13个方法的对象,相当于一个Object的子集。但是它将Object的一些命令式的操作转变成了函数式。

  • 比如 in操作符 这个就对应着Reflect.has
  • 比如 delete操作符 这个就对应着Reflect.deleteProperty

反射对象还有一个好处就是修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false

reflect对象的最大作用是和proxy代理对象的捕获器的方法是一一对应的,可以结合proxy代理对象一起使用

const target = {
  id: 'target'
}
const handler = {
  get(target, property, handler) {
      let a = ''
      if (property === 'id') {
          a = '!!!'
      }
      return Reflect.get(...arguments) + a
  }
}
const proxy = new Proxy(target, handler)
console.log(proxy.id) // target!!!

捕获器API

get()

get捕获器是再获取属性的时候触发的,对应的反射api是Reflect.get

get方法的返回值是没有限制的

拦截的操作

  • proxy.property
  • proxy[property]
  • Object.create(proxy)[property]
  • Reflect.get(proxy, property, receiver)

参数

  • target 目标对象
  • property 要查询的属性
  • proxy 代理对象
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 get(target, property, receiver) { 
 console.log('get()'); 
 return Reflect.get(...arguments) 
 } 
}); 
proxy.foo; 
// get()

set()

set捕获器是通过设置属性的时候触发的,对应的反射api是Reflect.set

set方法的返回值 返回 true 表示成功;返回 false 表示失败,严格模式下会抛出 TypeError

拦截操作

  • proxy.property = value
  • proxy[property] = value
  • Object.create(proxy)[property] = value
  • Reflect.set(proxy, property, value, receiver)

参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • value:要赋给属性的值。
  • receiver:接收最初赋值的对象。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 set(target, property, value, receiver) { 
 console.log('set()'); 
 return Reflect.set(...arguments) 
 } 
}); 
proxy.foo = 'bar'; 
// set()

has()

has捕获器是通过使用了in操作符触发的,对应的反射api是Reflect.has()

has方法必须返回布尔值,表示属性是否存在。返回非布尔值会被转型为布尔值

拦截操作

  • property in proxy
  • property in Object.create(proxy)
  • with(proxy) {(property);}
  • Reflect.has(proxy, property)

参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 has(target, property) { 
 console.log('has()'); 
 return Reflect.has(...arguments) 
 } 
}); 
'foo' in proxy; 
// has()

defineProperty()

defineProperty捕获器是通过使用了Object.defineProperty()中被触发,对应的反射api是Reflect.defineProperty()

defineProperty方法必须返回布尔值,表示属性是否成功定义。返回非布尔值会被转型为布尔值。

拦截操作

  • Object.defineProperty(proxy, property, descriptor)
  • Reflect.defineProperty(proxy, property, descriptor)

参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
  • descriptor:包含可选的 enumerable、configurable、writable、value、get 和 set定义的对象。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 defineProperty(target, property, descriptor) { 
 console.log('defineProperty()'); 
 return Reflect.defineProperty(...arguments) 
 } 
}); 
Object.defineProperty(proxy, 'foo', { value: 'bar' }); 
// defineProperty()

getOwnPropertyDescriptor()

getOwnPropertyDescriptor捕获器是通过使用了Object.getOwnPropertyDescriptor()中触发的,对应的反射api是Reflect.getOwnPropertyDescriptor()

getOwnPropertyDescriptor方法必须返回对象,或者在属性不存在时返回 undefined。

拦截操作

  • Object.getOwnPropertyDescriptor(proxy, property)
  • Reflect.getOwnPropertyDescriptor(proxy, property)

参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 getOwnPropertyDescriptor(target, property) { 
 console.log('getOwnPropertyDescriptor()'); 
 return Reflect.getOwnPropertyDescriptor(...arguments) 
 } 
}); 
Object.getOwnPropertyDescriptor(proxy, 'foo'); 
// getOwnPropertyDescriptor()

deleteProperty()

deleteProperty捕获器是通过使用了delete操作符触发的,对应的反射api是Reflect.deleteProperty()

deleteProperty方法必须返回布尔值,表示删除属性是否成功。返回非布尔值会被转型为布尔值。

拦截操作

  • delete proxy.property
  • delete proxy[property]
  • Reflect.deleteProperty(proxy, property)

参数

  • target:目标对象。
  • property:引用的目标对象上的字符串键属性。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 deleteProperty(target, property) { 
 console.log('deleteProperty()'); 
 return Reflect.deleteProperty(...arguments) 
 } 
}); 
delete proxy.foo 
// deleteProperty()

ownKeys()

ownKeys捕获器是通过Object.keys()及类似方法中被触发。对应的反射 API 方法为 Reflect.ownKeys()

拦截操作

  • Object.getOwnPropertyNames(proxy)
  • Object.getOwnPropertySymbols(proxy)
  • Object.keys(proxy)
  • Reflect.ownKeys(proxy)

参数

  • target:目标对象。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 ownKeys(target) { 
 console.log('ownKeys()'); 
 return Reflect.ownKeys(...arguments) 
 } 
}); 
Object.keys(proxy); 
// ownKeys()

getPrototypeOf()

getPrototypeOf捕获器是通过在 Object.getPrototypeOf()获取原型对象中被触发的,对应的Reflect.getPrototypeOf()

getPrototypeOf()必须返回对象或 null。

拦截操作

  • Object.getPrototypeOf(proxy)
  • Reflect.getPrototypeOf(proxy)
  • proxy.proto
  • Object.prototype.isPrototypeOf(proxy)
  • proxy instanceof Object

参数

  • target:目标对象。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 getPrototypeOf(target) { 
 console.log('getPrototypeOf()'); 
 return Reflect.getPrototypeOf(...arguments) 
 } 
}); 
Object.getPrototypeOf(proxy); 
// getPrototypeOf()

setPrototypeOf()

setPrototypeOf捕获器是通过使用Object.setPrototypeOf()设置原型对象中触发的,对应的Reflect.setPrototypeOf()

setPrototypeOf方法必须返回布尔值,表示原型赋值是否成功。返回非布尔值会被转型为布尔值

拦截操作

  • Object.setPrototypeOf(proxy)
  • Reflect.setPrototypeOf(proxy)

参数

  • target:目标对象。
  • prototype:target 的替代原型,如果是顶级原型则为 null。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 setPrototypeOf(target, prototype) { 
 console.log('setPrototypeOf()'); 
 return Reflect.setPrototypeOf(...arguments) 
 } 
}); 
Object.setPrototypeOf(proxy, Object); 
// setPrototypeOf()

isExtensible

isExtensible捕获器的通过Object.isExtensible()是否可扩展中被调用。对应的反射 API 方法为Reflect.isExtensible()

isExtensible()必须返回布尔值,表示 target 是否可扩展。返回非布尔值会被转型为布尔值。

拦截操作

  • Object.isExtensible(proxy)
  • Reflect.isExtensible(proxy)

参数

  • target:目标对象。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 isExtensible(target) { 
 console.log('isExtensible()');
 return Reflect.isExtensible(...arguments) 
 } 
}); 
Object.isExtensible(proxy); 
// isExtensible()

preventExtensions

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

preventExtensions()必须返回布尔值,表示 target 是否已经不可扩展。返回非布尔值会被转型为布尔值

拦截操作

  • Object.preventExtensions(proxy)
  • Reflect.preventExtensions(proxy)

参数

  • target:目标对象。
const myTarget = {}; 
const proxy = new Proxy(myTarget, { 
 preventExtensions(target) { 
 console.log('preventExtensions()'); 
 return Reflect.preventExtensions(...arguments) 
 } 
}); 
Object.preventExtensions(proxy); 
// preventExtensions()

apply

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

拦截操作

  • proxy(...argumentsList)
  • Function.prototype.apply(thisArg, argumentsList)
  • Function.prototype.call(thisArg, ...argumentsList)
  • Reflect.apply(target, thisArgument, argumentsList)

参数

  • target:目标对象。
  • thisArg:调用函数时的 this 参数。
  • argumentsList:调用函数时的参数列表
const myTarget = () => {}; 
const proxy = new Proxy(myTarget, { 
 apply(target, thisArg, ...argumentsList) { 
 console.log('apply()'); 
 return Reflect.apply(...arguments) 
 } 
}); 
proxy(); 
// apply()

construct

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

construct()必须返回一个对象

拦截操作

  • new proxy(...argumentsList)
  • Reflect.construct(target, argumentsList, newTarget)

参数

  • target:目标构造函数。
  • argumentsList:传给目标构造函数的参数列表。
  • newTarget:最初被调用的构造函数。
const myTarget = function() {}; 
const proxy = new Proxy(myTarget, { 
 construct(target, argumentsList, newTarget) { 
 console.log('construct()'); 
 return Reflect.construct(...arguments) 
 } 
}); 
new proxy; 
// construct()