参考:阮一峰Proxy教程
demos代码 GitHub 地址:https://github.com/huajianduzhuo/es6/tree/master/proxy
Proxy
Proxy 用于在目标对象之前架设一层拦截,外界对对象的访问,都必须经过这层拦截。因此可以对外界的访问进行过滤和改写。
Proxy 构造函数
通过 Proxy 构造函数,来生成 proxy 实例。
1 | let target = {} |
target
表示需要被代理的目标对象,handler
用来定义拦截行为,也是一个对象。
1 | let obj = {} |
如上,通过定义 get
方法,用来拦截所有对 obj
对象的访问。因此虽然 obj
对象是一个空对象,但是访问 aaa
属性,仍能得到结果 29.
需要注意的是,要使代理起作用,访问 obj 目标对象时,要通过
proxy
实例,直接使用目标对象 obj,代理无作用。
如果 handler 是一个空对象,没有设置任何拦截,则访问 proxy 实例等同于访问 target 目标对象。
1 | let obj2 = {} |
handler 代理配置方法
在 handler 中可以设置的拦截操作如下
get
get (target, key, receiver)
target 为目标对象,key 为属性名,receiver 为原始的读操作所在的对象,一般是 proxy 实例。
拦截访问对象的属性的操作,如 proxy.foo 或 proxy[‘foo’]
1 | let obj = { |
receiver 有时候不是 proxy 实例,比如当 proxy 实例作为原型的时候,这时 receiver 指向原始的读操作所在的对象
1 | let pr = new Proxy({}, { |
set
set (target, key, value, receiver)
拦截对对象属性的赋值操作,如 proxy.foo = 'aaa'
作用:
- 赋值时对属性值进行校验
- 设置私有属性
1 | function invariant(key, action) { |
set
方法不 return true,在严格模式下会报错。
has
has (target, key)
拦截 key in proxy
操作,返回一个布尔值
has
拦截对for...in
循环不生效
1 | let src = {name: '白宇'} |
deleteProperty
deleteProperty (target, key)
拦截 delete proxy[key]
操作,返回一个布尔值
如果该方法返回 false 或报错,则属性无法被删除
1 | let obj = {_name: 'white', name: '白宇'} |
ownKeys
ownKeys (target)
拦截以下操作,返回一个数组
Object.getOwnPropertyNames(proxy)
Object.getOwnPropertySymbols(proxy)
Object.keys(proxy)
for...in
getOwnPropertyDescriptor
getOwnPropertyDescriptor (target, key)
拦截 Object.getOwnPropertyDescriptor(proxy, key)
操作
defineProperty
defineProperty (target, key, propDesc)
拦截对 target 添加属性、为属性赋值对操作,不拦截删除属性对操作
return false
则对属性的操作不生效
1 | let p = new Proxy({age: 29}, { |
preventExtensions
preventExtensions (target)
拦截 Object.preventExtensions(proxy)
操作
getPrototypeOf
getPrototypeOf (target)
拦截下列操作
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
1 | let myProto = {name: '白宇'} |
返回值必须是对象或 null。如果目标对象不可扩展,则必须返回目标对象的原型对象。
isExtensible
isExtensible (target)
拦截 Object.isExtensible(proxy)
操作
- 必须返回布尔值,否则返回值会被自动转为布尔值
- 返回值必须与 target 的 isExtensible 属性保持一致,否则会抛出错误
setPrototypeOf
setPrototypeOf (target, proto)
拦截 Object.setPrototypeOf(proxy, proto)
操作
apply
apply (target, thisObj, args)
当 target 为函数时使用,拦截 proxy 作为函数调用的操作。如 proxy(...args)
、proxy.call(thisObj, ...args)
、proxy.apply(thisObj, args)
thisObj
为目标函数对上下文对象 this,args
为参数数组。
1 | function fun(name, age) { |
construct
construct (target, args)
当 target 为构造函数时使用,拦截 proxy 作为构造函数调用当操作。如 new proxy(...args)
1 | function Fun(firstName, lastName) { |
Proxy.revocable()
Proxy.revocable
方法返回一个可取消的 Proxy 实例。
1 | let {proxy, revoke} = Proxy.revocable({}, {}) |
Proxy.revocable
方法返回一个对象,该对象的 proxy
属性是 Proxy
实例,revoke
属性是一个函数,可以取消 Proxy
实例。上面代码中,当执行 revoke
函数之后,再访问 Proxy
实例,就会抛出一个错误。
Proxy.revocable
的一个使用场景是,目标对象不允许直接访问,必须通过代理访问,一旦访问结束,就收回代理权,不允许再次访问。
this 问题
Proxy 代理后,目标对象内部的方法中的 this
会指向代理对象
1 | let target = { |
此外,有些原生对象的内部属性,只有通过正确的
this
才能拿到,所以Proxy
也无法代理这些原生对象的属性。
1 | let t = new Date() |