关于javascript:javascript高级程序设计学习笔记-91代理基础

8次阅读

共计 6993 个字符,预计需要花费 18 分钟才能阅读完成。

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

代理根底

  • 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')) // true
console.log(proxy.hasOwnProperty('id')) // true
  • Proxy构造函数没有 prototype 属性,也不能应用 instanceof 操作符检测
console.log(Proxy) // [Function: Proxy]
console.log(Proxy.prototype) // undefined
console.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') // 1
Reflect.set(o2, 'foo', 3)
console.log(o2.foo) // 3
Reflect.has(o2, 'foo') // true
Reflect.deleteProperty(o2, 'bar')
console.log(o2.bar) // undefined
const 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 绑定到 o3
Function.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()) // false
console.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) // 123

const 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()办法?
  • 如何通过代理,在一个指标对象上构建多层拦挡网?
  • 代理有哪些潜在的问题?如何解决呢?
正文完
 0