一、从ECMAScript2015开始,JavaScript获得了Proxy和Reflect对象的支持,允许你拦截并定义基本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。
1、借助这两个对象,可以在JavaScript元级别进行编程。
代理
一、在ECMAScript6中引入的Proxy对象可以拦截某些操作并实现自定义行为。
【实例1】获取一个对象上的属性
let handler = {
get: function (target, name) {
return name in target ? target[name] : 42
}
}
let p = new Proxy({}, handler)
p.a = 1
console.log(p.a, p.b) // 1, 42
1、Proxy对象定义了一个目标(这里是一个空对象)和一个实现了get陷阱的handler对象。
代理的对象在获取未定义的属性时不会返回undefined, 而是返回42
术语
一、在讨论代理的功能时会用到以下术语
1、handler
包含陷阱的占位符对象
2、traps
提供属性访问的方法。这类似于操作系统中陷阱的概念
3、target
代理虚拟化的对象。它通常用作代理的存储后端。根据目标验证关于对象不可扩展性或不可配置属性的不变量(保持不变的语义)
4、invariants
实现自定义操作时保持不变的语义称为不变量。如果你违反处理程序的不变量,则会抛出一个TypeError
句柄和陷阱
一、Proxy对象可用的陷阱
Handler / trap | Interceptions | Invariants |
---|---|---|
handler.getPrototypeOf() | Object.getPrototypeOf() Reflect.getPrototypeOf() proto Object.prototype.isPrototypeOf() instanceof |
- getPrototypeOf方法一定返回一个对象或null. - 如果 target 不可扩展,Object.getPrototypeOf(proxy) 必须返回和 Object.getPrototypeOf(target)一样的值。 |
handler.setPrototypeOf() | Object.setPrototypeOf() Reflect.setPrototypeOf() |
如果 target 不可扩展,prototype 参数必须与Object.getPrototypeOf(target)的值相同。 |
handler.isExtensible() | Object.isExtensible() Reflect.isExtensible() |
Object.isExtensible(proxy) 必须返回和Object.isExtensible(target)一样的值。 |
handler.preventExtensions() | Object.preventExtensions() Reflect.preventExtensions() |
如果Object.isExtensible(proxy) 值为 false,Object.preventExtensions(proxy) 只返回true。 |
handler.getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor() Reflect.getOwnPropertyDescriptor() |
- getOwnPropertyDescripton 只能返回对象或者undefined. - A property cannot be reported as non-existent, if it exists as a non-configurable own property of the target object. - A property cannot be reported as non-existent, if it exists as an own property of the target object and the target object is not extensible. - A property cannot be reported as existent, if it does not exists as an own property of the target object and the target object is not extensible. - A property cannot be reported as non-configurable, if it does not exists as an own property of the target object or if it exists as a configurable own property of the target object. - The result of Object.getOwnPropertyDescriptor(target)can be applied to the target object using Object.defineProperty and will not throw an exception. |
handler.defineProperty() | Object.defineProperty() Reflect.defineProperty() |
- A property cannot be added, if the target object is not extensible. - A property cannot be added as or modified to be non-configurable, if it does not exists as a non-configurable own property of the target object. - A property may not be non-configurable, if a corresponding configurable property of the target object exists. - If a property has a corresponding target object property then Object.defineProperty(target, prop, descriptor) will not throw an exception. - In strict mode, a false return value from the defineProperty handler will throw a TypeErrorexception. |
handler.has() | Property query: foo in proxy Inherited property query: foo in Object.create(proxy) Reflect.has() |
- A property cannot be reported as non-existent, if it exists as a non-configurable own property of the target object. - A property cannot be reported as non-existent, if it exists as an own property of the target object and the target object is not extensible. |
handler.get() | Property access: proxy[foo]and proxy.bar Inherited property access: Object.create(proxy)[foo] Reflect.get() |
- The value reported for a property must be the same as the value of the corresponding target object property if the target object property is a non-writable, non-configurable data property. - The value reported for a property must be undefined if the corresponding target object property is non-configurable accessor property that has undefined as its [[Get]] attribute. |
handler.set() | Property assignment: proxy[foo] = barand proxy.foo = bar Inherited property assignment: Object.create(proxy)[foo] = bar Reflect.set() |
- Cannot change the value of a property to be different from the value of the corresponding target object property if the corresponding target object property is a non-writable, non-configurable data property. - Cannot set the value of a property if the corresponding target object property is a non-configurable accessor property that has undefinedas its [[Set]] attribute. - In strict mode, a false return value from the sethandler will throw a TypeError exception. |
handler.deleteProperty() | Property deletion: delete proxy[foo] and delete proxy.foo Reflect.deleteProperty() |
A property cannot be deleted, if it exists as a non-configurable own property of the target object. |
handler.enumerate() | Property enumeration / for…in: for (var name in proxy) {…} Reflect.enumerate() |
The enumerate method must return an object. |
handler.ownKeys() | Object.getOwnPropertyNames() Object.getOwnPropertySymbols() Object.keys() Reflect.ownKeys() |
- The result of ownKeys is a List. - The Type of each result List element is either String or Symbol. - The result List must contain the keys of all non-configurable own properties of the target object. - If the target object is not extensible, then the result List must contain all the keys of the own properties of the target object and no other values. |
handler.apply() | proxy(..args) Function.prototype.apply() and Function.prototype.call() Reflect.apply() |
There are no invariants for the handler.apply method. |
handler.construct() | new proxy(…args) Reflect.construct() |
结果一定是一个Object。 |
撤销Proxy
一、Proxy.revocable()方法被用来创建可撤销的Proxy对象。这意味着proxy可以通过revoke函数来撤销,并且关闭代理。此后,代理上的任意的操作都会导致TypeError
var revocable = Proxy.revocable({}, {
get: function (target, name) {
return '[[' + name + ']]'
}
})
var proxy = revocable.proxy
console.log(proxy.foo) // '[[foo]]'
revocable.revoke()
console.log(proxy.foo) // TypeError is thrown
proxy.foo = 1 // TypeError again
delete proxy.foo // still TypeError
typeof proxy // 'object', typeof doesn't trigger any trap
反射
一、Reflect是一个内置对象,它提供了可拦截JavaScript操作的方法。该方法和代理句柄类似,但Reflect方法并不是一个函数对象。
二、Reflect有助于将默认操作从处理程序转发到目标。
【实例1】以Reflect.has()为例,可以将in运算符作为函数
Reflect.has(Object, 'assign') // true
更好的apply函数
一、在ES5中,我们通常使用Function.prototype.apply()方法调用一个具有给定this值和arguments数组(或类数组对象)的函数。
Function.prototype.apply.call(Math.floor, undefined, [1.75])
1、使用Reflect.apply,这变得不那么冗长和容易理解
Reflect.apply(Math.floor, undefined, [1.75]);
// 1;
Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"
Reflect.apply(RegExp.prototype.exec, /ab/, ['confabulation']).index;
// 4
Reflect.apply(''.charAt, 'ponies', [3]);
// "i"
检查属性定义是否成功
一、使用Object.defineProperty,如果成功返回一个对象,否则抛出一个TypeError,你将使用try…catch块来捕获定义属性时发生的任何错误。因为Reflect.defineProperty返回一个布尔值表示的成功状态,你可以在这里使用if…else块
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}