关注前端小讴,浏览更多原创技术文章

代理根底

  • ES6 为的代理和反射为开发者提供拦挡并向基本操作嵌入额定行为的能力
  • 代理是指标对象的形象,其能够用作指标对象的替身,但齐全独立于指标对象
  • 指标对象既可间接被操作,也可通过代理来操作,间接操作会绕过代理施予的行为

相干代码 →

创立空代理

  • 应用Proxy构造函数创立代理,接管指标对象处理程序对象两个参数(缺一不可)
  • 空代理是最简略的代理,可用空对象作为处理程序对象,空代理对象仅作为一个形象的指标对象
const target = {  // 指标对象  id: 'target',}const handler = {} // 处理程序对象(空对象)const proxy = new Proxy(target, handler) // 创立空代理
  • (默认状况下)空代理对象上执行的所有操作都会利用到指标对象,反之亦然
console.log(target.id) // 'target'console.log(proxy.id) // 'target'target.id = 'foo' // 指标对象属性从新赋值console.log(target.id) // 'foo'console.log(proxy.id) // 'foo',会反映到代理上proxy.id = 'bar' // 代理属性从新赋值console.log(target.id) // 'bar',会反映到指标对象上console.log(proxy.id) // 'bar'console.log(target.hasOwnProperty('id')) // trueconsole.log(proxy.hasOwnProperty('id')) // true
  • Proxy构造函数没有prototype属性,也不能应用instanceof操作符检测
console.log(Proxy) // [Function: Proxy]console.log(Proxy.prototype) // undefinedconsole.log(proxy instanceof Proxy) // TypeError: Function has non-object prototype 'undefined' in instanceof check
  • 应用严格相等===用以辨别代理和指标
console.log(target === proxy) // false

定义捕捉器

  • 应用代理的次要目标是能够定义捕捉器,即基本操作的拦截器
  • 每个捕捉器对应一种基本操作,能够间接或间接在代理上调用,调用操作时会先调用捕捉器函数,再将操作流传到指标对象
const target2 = {  foo: 'bar',}const handler2 = {  // 定义get()捕捉器函数,以办法名为键  get() {    return 'handler override'  },}const proxy2 = new Proxy(target2, handler2)
  • get()函数能够通过多种形式触发并被捕捉器拦挡到:proxy[property]proxy.propertyObject.create(proxy)[property]
  • 只有代理对象上执行操作才会触发捕捉器,指标对象上不会
console.log(proxy2.foo) // 'handler override',代理对象上操作console.log(proxy2['foo']) // 'handler override',代理对象上操作console.log(Object.create(proxy2).foo) // 'handler override',代理对象上操作console.log(target2.foo) // 'bar',指标对象上操作console.log(target2['foo']) // 'bar',指标对象上操作console.log(Object.create(target2).foo) // 'bar',指标对象上操作

捕捉器参数和反射 API

get()捕捉器接管 3 个参数:指标对象、要查问的属性、代理对象

const target3 = {  foo: 'bar',}const handler3 = {  // get()捕捉器接管3个参数:指标对象、要查问的属性、代理对象  get(tar, pro, rec) {    console.log(tar === target3)    console.log(pro)    console.log(rec === handler3)  },}const proxy3 = new Proxy(target3, handler3)proxy3.foo/*   true  'foo'  false*/
  • 捕捉器利用这些参数重建被捕捉办法的原始行为
const handler4 = {  get(tar, pro, rec) {    return tar[pro] // target3['foo']  },}const proxy4 = new Proxy(target3, handler4)console.log(proxy4.foo) // 'bar'
  • 解决对象的所有捕捉器办法都有对应的反射 API 办法同名行为雷同),办法存在于全局对象Reflect
const handler5 = {  get() {    return Reflect.get(...arguments) // 用arguments解耦  },  // get: Reflect.get, // 更简洁的写法}const proxy5 = new Proxy(target3, handler5)console.log(proxy5.foo) // 'bar'
  • 创立一个能够捕捉所有办法,并将每个办法都转发给反射 API 的空代理,可不定义处理程序对象
const proxy6 = new Proxy(target3, Reflect)console.log(proxy6.foo) // 'bar'
  • 利用反射 API,可用起码的代码批改捕捉的办法
const target4 = {  foo: 'bar',  baz: 'qux',}const handler6 = {  get(tar, pro, rec) {    let dec = ''    pro === 'foo' && (dec = '!!!')    return Reflect.get(...arguments) + dec  },}const proxy7 = new Proxy(target4, handler6)console.log(proxy7.foo) // 'bar!!!'console.log(proxy7.baz) // 'qux'

捕捉器不变式

  • 捕捉处理程序的行为必须遵循捕捉器不变式
  • 如:指标对象有一个不可配置不可重写的属性,捕捉器批改返回值会报错
const target5 = {}Object.defineProperty(target5, 'foo', {  configurable: false, // 不可配置  writable: false, // 不可重写  value: 'bar',})const handler7 = {  get() {    return 'qux'  },}const proxy8 = new Proxy(target5, handler7)console.log(proxy8.foo) // 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')

可撤销代理

  • new Proxy()创立的代理不可撤销,会在代理对象生命周期内始终存在
  • Proxy.revocable()办法能够用来创立一个可撤销的代理对象

    • 接管指标对象解决对象2 个参数
    • 返回构造为{"proxy":proxyObj,"revoke":revokeFun}的对象
    • proxy为代理对象;revoke为撤销办法,调用时不需参数
    • 撤销函数revoke()幂等,调用屡次后果雷同
    • 撤销操作不可逆,撤销后再次调用代理会报错
const target6 = {  foo: 'bar',}const handler8 = {  get() {    return 'intercepted'  },}const revocable = Proxy.revocable(target6, handler8)const proxy9 = revocable.proxy // 创立可撤销代理console.log(proxy9.foo) // 'intercepted'revocable.revoke() // 撤销代理revocable.revoke() // 撤销代理,调用屡次后果雷同revocable.revoke() // 撤销代理,调用屡次后果雷同// console.log(proxy9.foo) // TypeError: Cannot perform 'get' on a proxy that has been revoked

实用反射 API

反射 API 与对象 API

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

    • Object上的办法实用于通用程序,反射办法实用于细粒度的对象管制与操作

状态标记

  • 以下反射办法提供状态标记,返回布尔值示意操作是否胜利

    • Reflect.defineProperty()Reflect.preventExtensions()Reflect.setPrototypeOf()Reflect.set()Reflect.deleteProperty()
    • (参数格局正确)操作失败时,不会抛出谬误,而是返回false
const o = {}Object.defineProperty(o, 'foo', {  writable: false, // 不可重写})Object.defineProperty(o, 'foo', { value: 'bar' }) // TypeError: Cannot redefine property: foo,Object.defineProperty()定义不胜利会抛出谬误Reflect.defineProperty(o, 'foo', { value: 'bar' }) // Reflect.defineProperty()定义不胜利不会抛出谬误console.log(Reflect.defineProperty(o, 'foo', { value: 'bar' })) // false,Reflect.defineProperty()返回“状态标记”的布尔值// 重构后的代码if (Reflect.defineProperty(o, 'foo', { value: 'bar' })) {  console.log('success')} else {  console.log('failure') // 'failure'}

用一等函数代替操作符

  • 以下反射办法提供只有通过操作符能力实现的操作

    • Reflect.get():能够代替对象属性拜访操作符
    • Reflect.set():能够代替赋值操作符=
    • Reflect.has():能够代替in操作符或with()
    • Reflect.deleteProperty():能够代替delete操作符
    • Reflect.construct():能够代替new操作符
const o2 = {  foo: 1,  bar: 2,  get baz() {    return this.foo + this.bar  },}Reflect.get(o2, 'foo') // 1Reflect.set(o2, 'foo', 3)console.log(o2.foo) // 3Reflect.has(o2, 'foo') // trueReflect.deleteProperty(o2, 'bar')console.log(o2.bar) // undefinedconst arr = Reflect.construct(Array, [1, 2, 3])console.log(arr) // [ 1, 2, 3 ]

平安地利用函数

  • 对函数原型对象Function.prototypeapply办法利用call进行绑定时,Reflect.apply()能够使代码更加简洁易懂
const f1 = function () {  console.log(arguments[0] + this.mark)}const o3 = {  mark: 95,}f1.apply(o3, [15]) // 110,将f1的this绑定到o3Function.prototype.apply.call(f1, o3, [15]) // 110,函数的原型对象的apply办法,利用call进行绑定Reflect.apply(f1, o3, [15]) // 110,通过指定的参数列表发动对指标函数的调用,三个参数(指标函数、绑定的this对象、实参列表)

无关 Reflect 对象的具体文档 →

代理另一个代理

  • 创立一个代理,通过它代理另一个代理,从而在一个指标对象上构建多层拦挡网
const target7 = {  foo: 'bar',}const firstProxy = new Proxy(target7, {  // 第一层代理  get() {    console.log('first proxy')    return Reflect.get(...arguments)  },})const secondProxy = new Proxy(firstProxy, {  // 第二层代理  get() {    console.log('second proxy')    return Reflect.get(...arguments)  },})console.log(secondProxy.foo)/*   'second proxy'  'first proxy'  'bar'*/

代理的问题与有余

代理中的 this

  • 代理中的this值是潜在的问题起源,例如办法中的this通常指向调用该办法的对象
const target8 = {  thisValEqualProxy() {    return this === proxy10    /*       this指向:      在实例中,指向实例自身      在代理中,指向代理对象    */  },}const proxy10 = new Proxy(target8, {})console.log(target8.thisValEqualProxy()) // falseconsole.log(proxy10.thisValEqualProxy()) // true
  • 指标对象依赖于对象标识时,this的指向会产生问题
const wm = new WeakMap()class User {  constructor(userId) {    wm.set(this, userId) // 应用指标对象作为WeakMap的键    /*       this的指向:指标对象    */  }  get id() {    return wm.get(this)    /*       this的指向:      在实例中,指向实例自身 User {}      在代理中,指向代理对象    */  }}const user = new User(123)console.log(wm) // WeakMap {User => 123}console.log(user.id) // 123const userInstanceProxy = new Proxy(user, {}) // 代理user实例,User类constructor中的this指向User类实例console.log(wm) // WeakMap {User => 123},弱键未发生变化console.log(userInstanceProxy.id) // undefined
  • 代理实例改为代理类自身,再创立代理实例,解决问题
const userClassProxy = new Proxy(User, {}) // 代理User类自身const proxyUser = new userClassProxy(456) // 创立代理实例,User类constructor中的this指向代理实例console.log(wm) // WeakMap {User => 123, User => 456},弱键发生变化,追加了以代理作为键console.log(proxyUser.id) // 456

代理与外部槽位

  • 有些内置类型可能会依赖代理无法控制的机制:如Date类型办法的执行依赖this值上的外部槽位[[NumberDate]],而该槽位不存在于代理对象,且无奈被get()set()操作拜访到
const target9 = new Date()const proxy11 = new Proxy(target9, {})console.log(target9.getDate()) // 24,当天日期console.log(proxy11.getDate()) // TypeError: this is not a Date object.

总结 & 问点

  • 代理的用途是什么?其和指标对象有怎么的关系?
  • 如何创立空代理?如何辨别空代理对象和指标对象?
  • 什么是捕捉器?其是如何被调用和触发的?get()函数能够通过哪些模式被捕捉器拦挡?
  • get()捕捉器接管哪些参数?写一段代码,利用这些参数重写捕捉办法的原始行为
  • 如何创立可撤销代理?撤销后再次撤销会怎么?撤销后调用代理会怎么?
  • 如何了解 Reflect 对象?其反射 API 与对象 API 有怎么的关联和异同?如何了解 Reflect.apply()办法?
  • 如何通过代理,在一个指标对象上构建多层拦挡网?
  • 代理有哪些潜在的问题?如何解决呢?