一、从ECMAScript2015开始,JavaScript获得了Proxy和Reflect对象的支持,允许你拦截并定义基本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。
1、借助这两个对象,可以在JavaScript元级别进行编程。

代理

一、在ECMAScript6中引入的Proxy对象可以拦截某些操作并实现自定义行为。
【实例1】获取一个对象上的属性

  1. let handler = {
  2. get: function (target, name) {
  3. return name in target ? target[name] : 42
  4. }
  5. }
  6. let p = new Proxy({}, handler)
  7. p.a = 1
  8. 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

  1. var revocable = Proxy.revocable({}, {
  2. get: function (target, name) {
  3. return '[[' + name + ']]'
  4. }
  5. })
  6. var proxy = revocable.proxy
  7. console.log(proxy.foo) // '[[foo]]'
  8. revocable.revoke()
  9. console.log(proxy.foo) // TypeError is thrown
  10. proxy.foo = 1 // TypeError again
  11. delete proxy.foo // still TypeError
  12. 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
}