handler.defineProperty()
defineProperty(target, propKey, propDesc)
用于拦截Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDesc)
、Reflect.defineProperty(...)
,返回一个布尔值。
let target = {} let handler = { defineProperty: function (target, propKey, propDescriptor) { console.log(`Called: ${propKey}`) return false } } let proxy = new Proxy(target, handler) let propDescriptor = { configurable: true, enumerable: true, value: 10 } Object.defineProperty(proxy, 'name', propDescriptor)
当调用 Object.defineProperty()
或者 Reflect.defineProperty()
,传递给 defineProperty
的 descriptor
有一个限制 - 只有以下属性才有用,非标准的属性将会被无视:
enumerable
configurable
writable
value
get
set
如果违背了以下的不变量,Proxy
会抛出TypeError
:
- 如果目标对象
target
不可扩展,将不能添加属性 - 不能添加或者修改一个属性为不可配置的,如果它不作为一个目标对象的不可配置的属性存在的话
- 如果目标对象存在一个对应的可配置属性,这个属性可能不会是不可配置的
- 如果一个属性在目标对象中存在对应的属性,那么
Object.defineProperty(target, propKey, propDescriptor)
将不会抛出异常 - 在严格模式下,
false
作为handler.defineProperty
方法的返回值的话将会抛出TypeError
异常
handler.getOwnPropertyDescriptor()
getOwnPropertyDescriptor(target, propKey)
用于拦截Object.getOwnPropertyDescriptor(proxy, propKey)
和Reflect.getOwnPropertyDescriptor(...)
,返回属性的描述对象或者undefined
。
let handler = { getOwnPropertyDescriptor: function (target, key) { console.log(`Called: ${key}`) if (key[0] === '_') { return } return Object.getOwnPropertyDescriptor(target, key) } } let target = { _prop: '大漠', prop: 'w3cplus' } let proxy = new Proxy(target, handler) console.log(Object.getOwnPropertyDescriptor(proxy, 'name')) console.log(Object.getOwnPropertyDescriptor(proxy, '_prop')) console.log(Object.getOwnPropertyDescriptor(proxy, 'prop'))
如果下列不变量被违反,代理将抛出一个 TypeError
:
getOwnPropertyDescriptor
必须返回一个object
或undefined
- 如果属性作为目标对象的不可配置的属性存在,则该属性无法报告为不存在。
- 如果属性作为目标对象的属性存在,并且目标对象不可扩展,则该属性无法报告为不存在
- 如果属性不存在作为目标对象的属性,并且目标对象不可扩展,则不能将其报告为存在
- 属性不能被报告为不可配置,如果它不作为目标对象的自身属性存在,或者作为目标对象的可配置的属性存在
Object.getOwnPropertyDescriptor(target)
的结果可以使用Object.defineProperty
应用于目标对象,也不会抛出异常
以下是 Object.getOwnPropertyDescriptor()
的代码违反了不变量:
let target = { name: '大漠' } Object.preventExtensions(target) let handler = { getOwnPropertyDescriptor: function (target, propKey) { console.log(`Called: ${propKey}`) return undefined } } let proxy = new Proxy(target, handler) console.log(Object.getOwnPropertyDescriptor(proxy, 'name'))
handler.getPrototypeOf()
getPrototypeOf(target)
用于拦截Object.getPrototypeOf(proxy)
、Reflect.preventExtensions(...)
、__proto__
、Object.isPrototypeOf()
、instanceof
,返回一个对象。
let target = {} let handler = { getPrototypeOf: function (target) { return Array.prototype } } let proxy = new Proxy(target, handler) console.log( `Object.getPrototypeOf(proxy): ${Object.getPrototypeOf(proxy) === Array.prototype}, Reflect.getPrototypeOf(proxy): ${Reflect.getPrototypeOf(proxy) === Array.prototype}, proxy.__proto__: ${proxy.__proto__ === Array.prototype}, Array.prototype.isPrototypeOf(proxy): ${Array.prototype.isPrototypeOf(proxy)}, proxy instanceof Array: ${proxy instanceof Array}`)
如果遇到了下面两种情况,JavaScript 引擎会抛出 TypeError
异常:
getPrototypeOf()
方法返回的不是对象也不是null
- 目标对象是不可扩展的,且
getPrototypeOf()
方法返回的原型不是目标对象本身的原型
下面的示例展示的两种情况下就会报异常:
let proxy1 = new Proxy( {}, { getPrototypeOf: function (target) { return '大漠' } } ) console.log(Object.getPrototypeOf(proxy1)) let proxy2 = new Proxy( Object.preventExtensions({}), { getPrototypeOf: function (target) { return {} } } ) console.log(Object.getPrototypeOf(proxy2))
handler.isExtensible()
isExtensible(target)
拦截Object.isExtensible(proxy)
和Reflect.isExtensible(...)
,返回一个布尔值。
let target = {} let handler = { isExtensible: function (target) { console.log(`Called`) return true } } let proxy = new Proxy(target, handler) console.log(Object.isExtensible(proxy))
这个方法有一个强限制,如果不能满足下面的条件,就会抛出错误。
Object.isExtensible(proxy) === Object.isExtensible(target)
下面是一个示例:
let target = {} let handler = { isExtensible: function (target) { console.log('Call') return false } } let proxy = new Proxy(target, handler) console.log(Object.isExtensible(proxy))
handler.ownKeys()
ownKeys(target)
用于拦截Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、Reflect.get()
和Reflect.ownKeys(...)
,返回一个数组。该方法返回对象所有自身的属性,而Object.keys()
仅返回对象可遍历的属性。
let target = {} let handler = { ownKeys: function (target) { console.log('Called') return ['a', 'b', 'c'] } } let proxy = new Proxy(target, handler) console.log(Object.getOwnPropertyNames(proxy))
如果违反了下面的约定,Proxy
将抛出错误 TypeError
:
ownKeys
的结果必须试一个数组- 数组的元素类型要么是一个
String
,要么是一个Symbol
- 结果列表必须包含目标对象的所有不可配置(non-configurable )、自有(
own
)属性的key
- 如果目标对象不可扩展,那么结果列表必须包含目标对象的所有自有(
own
)属性的key
,不能有其它值
下面的代码违反了约定,将会抛出TypeError
:
let target = {} Object.defineProperty( target, 'name', { configurable: false, enumerable: true, value: 10 } ) let handler = { ownKeys: function (target) { return [123, 12.5, true, false, undefined, null, {}, []] } } let proxy = new Proxy(target, handler) console.log(Object.getOwnPropertyName(proxy))
handler.preventExtensions()
preventExtensions(target)
用于拦截Object.preventExtensions(proxy)
和Reflect.preventExtensions(...)
,将返回一个布尔值。这个方法有一个限制,只有当Object.isExtensible(proxy)
为false
(即不可扩展)时,proxy.preventExtensions
才能返回true
,否则会报错。
let target = {} let handler = { preventExtensions: function(target) { return true } } let proxy = new Proxy(target, handler) console.log(Object.preventExtensions(proxy))
上面的代码,proxy.preventExtensions
方法返回true
,但这时Object.isExtensible(proxy)
会返回true
,因此报错。为了防止出现这个问题,通常要在proxy.preventExtensions
方法里面,调用一次Object.preventExtensions
。
let target = {} let handler = { preventExtensions: function (target) { console.log('Called') Object.preventExtensions(target) return true } } let proxy = new Proxy (target, handler) console.log(Object.preventExtensions(proxy))
handler.setPrototypeOf()
setPrototypeOf(target, proto)
用于拦截Object.setPrototypeOf(proxy, proto)
和Reflect.setPrototypeOf(...)
。将返回一个布尔值。
let target = function () {} let handler = { setPrototypeOf: function(target, propKey) { throw new Error('Changing the prototype is forbidden') } } let propKey = {} let proxy = new Proxy(target, handler) console.log(proxy.setPrototypeOf(proxy, propKey))
handler.apply()
apply(target, object, args)
用于拦截Proxy
实例作为函数调用的操作,比如proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
和Reflect.apply(...)
。
let target = function () {} let handler = { apply: function (target, thisArg, argumentsList) { console.log(`Called: ${argumentsList.join(',')}`) return argumentsList[0] + argumentsList[1] + argumentsList[2] } } let proxy = new Proxy (target, handler) console.log(proxy(1, 2, 3))
proxy.revocable()
Proxy
的revocable
的意思是,取消访问proxy
的权限. 这个有什么用呢? 没有什么卵用… 开玩笑的. 他的映射范围应该算是比较少的。但是往往却是精华. 现在,我们有这样一个场景,假如,你现在有一个敏感数据, 但如果突然发生错误,你想立即取消掉对该数据的访问,so? 一般,要不就是直接删除该数据。 但这样方式,太简单粗暴了。 而,proxy
提供了一个完美的trick
来帮助我们实现这一方式。使用revocable()
方法. 这时, 定义一个proxy
就需要使用 Proxy.revocable(target, handler)
。
let target = {}; let handler = {}; let {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; proxy.foo // 123 revoke(); proxy.foo // TypeError: Revoked
Proxy.revocable
方法返回一个对象,该对象的proxy
属性是Proxy
实例,revoke
属性是一个函数,可以取消Proxy
实例。上面代码中,当执行revoke
函数之后,再访问Proxy
实例,就会抛出一个错误。
Reflect
Reflect
对象与Proxy
对象一样,也是ES6为了操作对象而提供的新API。Reflect
对象的设计目的有这样几个。
- 将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。 - 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。 - 让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。 Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
一个对象的键们可以这样被访问:
Reflect.ownKeys(..)
:返回对象自己的键(不是通过“继承”的),同样也返回Object.getOwnPropertyNames()
和Object.getOwnPropertySymbols()
Reflect.enumerate(..)
:返回对象上可枚举的属性(包括“继承”过来的)。Reflect.has(..)
:和in
操作符差不多。
函数的调用和构造调用可以手动通过下面 API 执行:
Reflect.apply(..)
:举个例子,Reflect.apply(foo,thisObj,[42,"bar"])
调用了foo()
函数,上下文是thisObj
,参数为42
和bar
。Reflect.construct(..)
:举个例子,Reflect.construct(foo,[42,"bar"])
等于new foo(42,"bar")
。
对象属性访问,设置,删除可以手动通过下面 API 执行:
Reflect.get()
:Reflect.get(o,"foo")
等于o.foo
。Reflect.set()
:Reflect.set(o,"foo",42)
等于o.foo = 42
。Reflect.deleteProperty()
:Reflect.deleteProperty(o,"foo")
等于delete o.foo
。
Reflect
的元编程能力可以让你等效的模拟以前隐藏的各种语法特性。这样你就可以使用这些功能为特定语言(DSL)拓展新功能和 API。
总结
这篇文章主要整理了有关于JavaScript中的Proxy
和Reflect
相关的知识点。整理的有点混乱,如果你有更好的经验或者说文章中有不对之处,还请在下面的评论中与我一起分享。