代理是 ECMAScript 6 新增的令人兴奋和动态十足的新特性。尽管不支持向后兼容,但它开辟出了 一片前所未有的 JavaScript 元编程及抽象的新天地。

从宏观上看,代理是真实 JavaScript 对象的透明抽象层。代理可以定义包含捕获器的处理程序对象, 而这些捕获器可以拦截绝大部分 JavaScript 的基本操作和方法。在这个捕获器处理程序中,可以修改任 何基本操作的行为,当然前提是遵从捕获器不变式。
与代理如影随形的反射 API,则封装了一整套与捕获器拦截的操作相对应的方法。可以把反射 API 看作一套基本操作,这些操作是绝大部分 JavaScript 对象 API 的基础。
代理的应用场景是不可限量的。开发者使用它可以创建出各种编码模式,比如(但远远不限于)跟 踪属性访问、隐藏属性、阻止修改或删除属性、函数参数验证、构造函数参数验证、数据绑定,以及可 观察对象。

代理 Proxy

拦截并向基本操作嵌入额外行为的能力:
具体地说,可以给目标对象定义一个关联的代理对象,而这个代理对象可以作为抽象的目标对象来使用。在对目标对象的各种操作影响目标对象之前,可以在代理对象中对这些操作加以控制。

代理是目标对象的抽象、替身。

创建代理、空代理

代理是使用 Proxy 构造函数创建的。

  1. const target = {
  2. name: '目标对象'
  3. }
  4. const handler = {
  5. name: '处理程序对象'
  6. }
  7. const proxyObject = new Proxy(target, handler) // 代理对象
  8. console.log(proxyObject)
  9. console.log(proxyObject.name)

image.png

函数签名:

1、目标对象(必填)
2、处理程序对象(必填)

空代理:

什么也不做的代理对象。
第二个参数可以传一个简单的对象字面量,什么也不做。
空代理就是个“假门神”,最后对代理对象的操作都会转嫁到目标对象上。

  1. const target = {
  2. id: 'target',
  3. }
  4. const handler = {}
  5. const proxy = new Proxy(target, handler)
  6. console.log(target, proxy)
  7. // id 属性会访问同一个值
  8. console.log(target.id) // target
  9. console.log(proxy.id) // target
  10. // 给目标属性赋值会反映在两个对象上
  11. // 因为两个对象访问的是同一个值
  12. target.id = 'foo'
  13. console.log(target.id) // foo
  14. console.log(proxy.id) // foo
  15. // 给代理对象的属性赋值会反映在两个对象上
  16. // 因为这个赋值会转移到目标对象(空代理,对代理的操作都会转移到目标对象)
  17. proxy.id = 'bar' // 相当于给目标对象的id重新赋值
  18. console.log(target.id) // bar
  19. console.log(proxy.id) // bar

image.png

hasOwnProperty

也会应用到目标对象

  1. const target = {
  2. id: 'target',
  3. }
  4. const handler = {}
  5. const proxy = new Proxy(target, handler)
  6. // hasOwnProperty()方法在两个地方
  7. // 都会应用到目标对象
  8. console.log(target.hasOwnProperty('id')) // true
  9. console.log(proxy.hasOwnProperty('id')) // true。这里也返回true就说明了

尽管如此,二者不完全相等

代理对象 !== 目标对象

  1. console.log(target === proxy) // false

没有 Proxy.prototype

Proxy构造函数没有prototype属性,直接获取得到undefined。
image.png

  1. console.log(Proxy.prototype) // undefined

不能用instanceof

  1. // Proxy.prototype 是 undefined
  2. // 因此不能使用 instanceof 操作符
  3. console.log(target instanceof Proxy) // TypeError: Function has non-object prototype 9 'undefined' in instanceof check
  4. console.log(proxy instanceof Proxy) // TypeError: Function has non-object prototype 'undefined' in instanceof check

image.png

捕获器(trap)

handler 参数里配置的操作函数
一个 handler 处理程序对象里可以有多个操作函数。

捕获器:get(目标对象, 要查询的属性, 代理对象)

有了这些参数,就可以重建被捕获方法的原始行为

  1. const target = {
  2. foo: 'bar',
  3. }
  4. const handler = {
  5. // 捕获器在处理程序对象中以方法名为键
  6. get(trapTarget, property, receiver) {
  7. return 'handler override'
  8. },
  9. }
  10. const proxy = new Proxy(target, handler)
  11. console.log(target.foo) // bar
  12. console.log(proxy.foo) // handler override
  13. console.log(target['foo']) // bar
  14. console.log(proxy['foo']) // handler override
  15. console.log(Object.create(target)['foo']) // bar
  16. console.log(Object.create(proxy)['foo']) // handler override

proxy[property]、proxy.property 或 Object.create(proxy)[property]等
所有这些操作只要发生在代理对象上,就会触发 get()捕获器。
发生在目标对象上,因为不会经过代理对象所以不会触发。

反射 Reflect

一个捕获器因为有足够多的参数,所以可以手写需要的需求,实现代理对象的功能(原始操作:比如原本就要获取target的属性,通过get捕获器实现如下:)

  1. const handler = {
  2. // 捕获器在处理程序对象中以方法名为键
  3. get(trapTarget, property, receiver) {
  4. return trapTarget[property]
  5. },
  6. }

但是,js提供了全局对象Reflect,该对象上封装了原始行为的同名方法来帮助我们实现代理对象的原始功能。

反射方法与捕获器方法具有相同的函数名和函数签名

处理程序对象中所有可以捕获的方法都有对应的反射(Reflect)API 方法。
这些方法与捕获器拦截的方法具有相同的名称和函数签名(参数也一致),而且也具有与被拦截方法相同的行为。
因此,使用反射 API 也可以像下面这样定义出空代理对象:

  1. const handler = {
  2. get() {
  3. return Reflect.get(...arguments) // 如同 Reflect.get(target, property, receiver)
  4. },
  5. }
  6. // 更简洁写法
  7. const handler = {
  8. get: Reflect.get
  9. };

可以直接用Reflect实现空代理

不需要定义处理程序对象,直接创建一个可以捕获所有方法,然后将每个方法转发给对应反射 API 的空代理

  1. const target = {
  2. foo: 'bar'
  3. };
  4. const proxy = new Proxy(target, Reflect);
  5. console.log(proxy.foo); // bar
  6. console.log(target.foo); // bar

释放你的想象力,随便玩儿

  1. const target = {
  2. foo: 'bar',
  3. baz: 'qux',
  4. }
  5. const handler = {
  6. get(trapTarget, property, receiver) {
  7. let decoration = ''
  8. if (property === 'foo') {
  9. decoration = '!!!'
  10. }
  11. console.log(111, Reflect.get(...arguments))
  12. return Reflect.get(...arguments) + decoration
  13. },
  14. }
  15. const proxy = new Proxy(target, handler)
  16. console.log(proxy.foo) // bar!!!
  17. console.log(proxy.baz) // qux
  18. console.log(target.foo) // bar

但也不能玩的过火,注意分寸,别得意忘形,不记得自己啥身份~ 一个代理对象还想偷梁换柱?!

捕获器不变式 (trap invariant)

捕获器不变式因方法不同而异,但通常都会防止捕获器定义出现过于反常的行为

比如,如果目标对象有一个不可配置且不可写的数据属性,那么在捕获器返回一个与该属性不同的值时,会抛出 错误:TypeError

  1. const targetConstant = {}
  2. Object.defineProperty(targetConstant, 'foo', {
  3. configurable: false,
  4. writable: false,
  5. value: 'bar',
  6. })
  7. const handlerObj = {
  8. get() {
  9. return 'qux' // 报错:Uncaught TypeError: 'get' on proxy: property 'foo' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected 'bar' but got 'qux')
  10. // return 'bar' // 这样直接返回字符串,也是正常的
  11. },
  12. }
  13. const proxyObj = new Proxy(targetConstant, handlerObj)
  14. console.log(proxyObj.foo) // TypeError

感觉这种写法可以用到组件内部,防止外部使用代理对象把我们重要的数据给移花接木了

可撤销代理

世界上没有后悔药,但是代码里有

对于使用 new Proxy()创建的普通代理来说,这种联系会在代理对象的生命周期内一直持续存在。

但我们还可以用另一种方式创建可撤销代理

revocable()方法

这个方法支持撤销代理对象与目标对象的关联。
撤销代理的操作是不可逆的。
撤销函数(revoke())是幂等的,调用多少次的结果都一样。
撤销代理之后 再调用代理会抛出 TypeError。

  1. const target = {
  2. name: '目标对象'
  3. }
  4. const handler = {
  5. get() {
  6. return '处理程序对象'
  7. }
  8. }
  9. // 终身代理,永久有效
  10. const proxy = new Proxy(target, handler)
  11. console.log(proxy.name)
  12. // 可撤销的代理
  13. const { proxy: proxy1, revoke } = Proxy.revocable(target, handler)
  14. console.log(proxy1, proxy1.name)
  15. console.log(target.name)
  16. revoke() // 执行撤销,罢免代理
  17. console.log(proxy1, proxy1.name) // TypeError: Cannot perform(执行) 'get' on a proxy that has been revoked

没想明白撤销有什么用

扩展理解:

设计模式中的代理模式

一个比较通俗的例子:明星经纪人,就是明星对象的代理对象。针对粉丝对明星的一些操作,做一层保护代理和虚拟代理。
地址:https://www.cnblogs.com/dengyao-blogs/p/11713185.html

Nginx部署的代理服务器

做的就是网络分发、缓存、提速、安全保障等代理的工作

生活中,代理商的身份和做的事情