ES6 Proxy元编程

译自:Metaprogramming with proxies

Proxy 是什么

Proxy 使你能够拦截和定制对象执行的操作(如获取属性),是一种元编程特性。

下面的例子中,Proxy是一个我们正在拦截操作的代理对象,handler 是处理拦截操作的对象。

在这个例子中,我们只拦截一个简单的操作: get

1
2
3
4
5
6
7
8
9
const target = {}
const handler = {
get(target, propKey, receiver) {
console.log(`get ${propKey}`)
return 123;
}
}
const proxy = new Proxy(target, handler);
proxy.name // get name

编程和元编程

在我们了解 Proxy 是什么,以及它为什么有用之前,应该先来了解下什么是元编程。

编程,可以分为以下两个级别:

  • 基础级别(Base level也叫,程序级别),代码用来处理用户的输入,即上层应用级别的代码
  • 元级别(Meta level),其中的代码用来处理基础基础级别的代码,更接近于底层

基础级别和元级别的代码可能来自于不同语言的,下面这段元程序代码中,元编程语言是 JavaScript,基础编程语言是 Java

1
2
const str = 'Hello' + '!'.repeat(3);
console.log('System.out.println("'+str+'")');

元编程可以采取不同的形式,在先前的例子中,我们使用 console 打印了 Java 的代码。让我们使用 JavaScript 分别用于元编程语言和基础编程语言,典型的例子就是 eval() 函数,它能够动态的对 JavaScript 代码进行求值编译。当然 eval() 在实际中并不太推荐使用,所以实际的应用场景并不多。

下面示例中,我们使用 eval() 对 5 + 2 进行求值运算

1
console.log(eval(5 + 2)) // 7

其他的一些 JavaScript 看上去不像元编程,如果仔细看的话,它们确实是:

1
2
3
4
5
6
7
8
9
10
// Base level
let obj = {
hello() {
console.log('hello')
}
}
// Meta level
for (const key of Object.keys(obj)) {
console.log(key)
}

这段程序在运行时,遍历自己的结构。这看起来不太像元编程,因为在 JavaScript 中,程序结构和数据结构的概念是比较模糊的。所有的 Object.* 的方法是可以被看做是元编程函数的

元编程的种类

反射元编程,是指程序处理自身的过程。三种反射元编程的区别:

  • 内省(Introspection):运行时检查对象类型
  • 自修改(Self-modification):可以用来改变程序结构
  • 反射(Intercession):可以用来重新定义一些语言层面的操作

看一些例子

内省Object.keys() 就是执行了自省操作,用来读取一个结构体(看看先前的例子)

自修改:下面的函数 movePropertysource 属性转移到 target 中,通过括号操作符对属性进行访问、赋值操作符和删除操作符进行自修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
function moveProperty(source, propertyName, target) {
target[propertyName] = source[propertyName];
delete source[propertyName];
}
let obj1 = {
prop: 'abc'
}
let obj2 = {}
moveProperty(obj1, 'prop', obj2);
console.log(obj1)
console.log(obj2)

// { prop: 'abc' }

ES5 中不支持调解,但是 Proxy 却填补了这一空白。

Proxy 用法

ES6 中的 ProxyJavaScript 带来了反射(Intercession),它的工作原理如下,可以对对象 obj 执行许多操作,例如:

  • 获取对象 objprop 属性(obj.prop
  • 检查对象 obj 是否存在 prop 属性('prop' in obj

Proxy 是一个特殊的对象,允许我们定制一些对象的操作, Proxy 构造函数需要两个参数:

  • target:如果处理程序 handler 没有任何拦截某个操作,那么该操作将会使用原目标对象(target)的。也就是说 targetProxy 提供了后备操作。某种程度上,Proxy 包装了目标对象 target

  • handler:每个操作,都有相应的处理程序(handler)方法执行该操作,这种方法拦截操作,成为陷阱(trap)(借用词汇:操作系统陷阱

下面的例子中,handler 拦截了 gethas 操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const target = {}
const handler = {
get(target, propKey, receiver) {
console.log(`GET ${propKey}`)
return 110
},
has(target, propKey) {
console.log(`HAS ${propKey}`)
return true
}
}
const proxy = new Proxy(target, handler)
proxy.too
// GET too
'demo' in proxy
// HAS demo

handler 没有没有实现陷阱 set ,因此,设置 proxy.bar 将会指向 target,并对 target.bar 进行设置

1
2
3
proxy.bar = 'abc'
console.log(target.bar)
// abc

函数专用的陷阱(trap)

如果 target 是一个函数,可以使用另外两个拦截操作:

  • applay 进行函数调用, 通过以下方式:

    • proxy()
    • proxy.call(···)
    • proxy.apply(···)
  • construct 构造函数调用,通过以下方式:

    • new proxy(···)

这两个 trap 只对函数有效的原因是很简单的,因为除此之外,你也不能将这些 trap 应用到其他对象上不是吗?

拦截方法调用

如果你想通过 Proxy 拦截一个方法调用,这将会是一个挑战:你可以拦截一个 get 操作,也可以拦截 apaly 操作,但是关于函数调用的拦截是没有的。

因为函数调用通常被看做两个分离的操作:首先 get 检索一个函数,然后使用 apply 调用函数

因此,我们必须拦截 get,并返回一个拦截函数调用的函数。下面的代码演示了如何做到这一点:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function traceMethodCall(obj) {
const handler = {
// 通过 get 检索调用的属性
get(target, propKey, receiver) {
const orgMethod = target[ propKey ]
// 访问属性返回函数,以便该属性可以作为函数调用
return function (...args) {
// 将被拦截函数调用的结果返回,this 指向 proxy
const result = orgMethod.apply(this, args)
console.log(propKey + JSON.stringify(args) + ' -> ' + JSON.stringify(result))
return result
}
}
}
return new Proxy(obj, handler)
}

让我们使用下面的 obj 来试验一下 traceMethodCalls()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const obj = {
multiply(x, y) {
return x * y
},
squared(x) {
return this.multiply(x, x)
}
}
const traceObj = traceMethodCall(obj)
traceObj.multiply(2, 5)
// multiply[2,5] -> 10
traceObj.squared(4)
// multiply[4,4] -> 16
// squared[4] -> 16

可以看到,traceObjobj 的一个函数被追踪版本。

很好的一点是,即使在 traceObj.squared()中调用 this.traceObj, 它同样也会被跟踪。这是因为 this 一直都在引用 proxy

当然,这又不是特别有效的方法方案,例如,可以使用缓存方法。此外,代理本身对性能有影响。

可撤销(revocable)的 Proxy

ES6 中,可以创建 可撤销的 Proxy

1
const {proxy, revoke} = Proxy.revocable(target, handler);

在运算符= 左边,我们使用解构 的方式得到 Proxy.revocable 返回的 proxyrevoke

首次调用 revoke() 后,作用于 proxy 的任何操作都将抛出异常,后续的 revoke() 调用,将没有任何效果:

1
2
3
4
5
6
7
const target = {}
const handler = {}
const { proxy, revoke } = Proxy.revocable(target, handler)
proxy.foo = 123
console.log(proxy.foo) // 123
revoke()
console.log(proxy.foo) // TypeError: Revoked

将代理(Proxy)作为原型(prototype)

代理对象 proto 可以作为对象 obj 的原型,从 obj 执行的一些操作可能指向 proto ,例如下面的 get 操作:

1
2
3
4
5
6
7
8
9
const proto = new Proxy({}, {
get(target, propKey, receiver) {
console.log(`GET ${propKey}`)
return target[ propKey ]
}
})
const obj = Object.create(proto)
obj.foo
// GET foo

obj 中并不存在 foo 属性,因此会继续向原型对象 proto 中查找,然后出发陷阱(tap) get

转发拦截操作

处理程序 hanlder 未实现陷阱trap的操作,将自动转发的目标对象 target。有时候除了转发操作,我们还想执行一些其他的操作,例如:handler 拦截了所有操作,并进行打印,但是不会去阻止操作专项目标对象 target

1
2
3
4
5
6
7
8
9
10
const handler = {
deleteProperty(target, propKey) {
console.log(`GET ${propKey}`)
return delete target[ propKey ]
},
has(target, propKey) {
console.log(`HAS ${propKey}`)
return propKey in target
}
}

对于每个陷阱,我们首先记录操作的名称,然后通过手动执行将其转发。ES 6有类似模块的对象 Reflect,这有助于为每个陷阱(trap)转发

1
handler.trap(target, arg_1, ···, arg_n)

Reflect 使用形式如下(其中trap指代拦截的方法统称,非Reflect 的方法):

1
Reflect.trap(target, arg_1, ···, arg_n)

如果我们使用 Reflect ,那么先前的例子应该看起来像下面这样:

1
2
3
4
5
6
7
8
9
10
const handler = {
deleteProperty(target, propKey) {
console.log(`GET ${propKey}`)
return Reflect.deleteProperty(target, propKey)
},
has(target, propKey) {
console.log(`HAS ${propKey}`)
return Reflect.has(target, propKey)
}
}

不是所有的对象都可以通过代理透明包装

handler 可以看作是对其目标对象(target)执行的拦截操作——代理包装目标。代理(Proxy)的处理程序对象(handler)类似于代理的观察者或侦听器。它通过指定实现相应的方法(读取属性的get等)来拦截哪些操作。如果缺少操作的处理程序方法,则不会拦截该操作。它只是被转发到目标。

因此,如果处理程序handler是空对象,代理可以透明地包装目标。但是,这并不总是奏效的

包装对象会影响到 this

在深入之前,让我们快速回顾一下包装目标 target 是如何影响 this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const target = {
foo() {
return {
thisIsTarget: this === target,
thisIsProxy: this === proxy,
}
}
}
const handler = {}
const proxy = new Proxy(target, handler)
console.log(target.foo())
// { thisIsTarget: true, thisIsProxy: false }
console.log(proxy.foo())
// { thisIsTarget: false, thisIsProxy: true }

可以看到,如果使用 target 调用 target.foo()this 是指向 target 对象的;如果我们使用 proxy 调用 proxy.foo()this 是指向 proxy 对象的。

这样做的目的其实是,如果目标对象(target) this 调用方法,那么 proxy 也会在循环中保持其 this 不变。

无法透明包装的对象

通常,代理的处理程序对象handler 为空对象时,代理将不会改变目标对象 target 的行为。

但是,如果目标对象 target 没有任何拦截(handler{})的Proxy 包裹,那么我们会遇到一个问题:

事情会变的跟我们想的不一样,因为对象的信息关联依赖于目标对象target 是否被包装。

例如,下面 Person 对象的私有属性(WeakMap_name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const _name = new WeakMap();
class Person {
constructor(name) {
_name.set(this, name)
}
get name() {
return _name.get(this)
}
}
const jclee = new Person('jclee')
console.log(jclee.name)
// jclee

const proxy = new Proxy(jclee, {})
console.log(proxy.name)
// undefined

可以看到,具有私有属性的 Person 实例,被没有任何拦截的 Proxy 包装之后,会使我们访问不到私有属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person2 {
constructor(name) {
this._name = name;
}
get name() {
return this._name;
}
}

const jane = new Person2('Jane');
console.log(jane.name); // Jane

const proxy = new Proxy(jane, {});
console.log(proxy.name); // Jane

不适用私有属性,就不存上面说的问题。

包装内置构造函数的实例

大多数内置构造函数,也是不能被 Proxy 所包装的,他们因此也不能被显示的包装。例如 Date 构造函数:

1
2
3
4
5
6
const target = new Date();
const handler = {};
const proxy = new Proxy(target, handler);

proxy.getDate();
// TypeError: this is not a Date object.

这种不受 Proxy 影响的机制称为内部槽,这些内部槽是与实例相关联的伪属性。规范处理这些插槽,就好像它们是用方括号括起来的属性一样,例如,下面的方法是内部的,可以在所有对象 o 上调用:

1
O.[[GetPrototypeOf]]()

但是,访问内部插槽并不是通过正常的 getset 操作进行的。如果 getDate() 是通过代理调用的,它就无法在 this 中找到它需要的内部槽位,并会报 TypeError

对于 Date 方法,语言规范声明:

除非另有明确说明,下面定义的 Number 原型对象的方法不是通用的,传递给它们的这个值必须是一个 Number 值或一个具有 [[NumberData]] 内部槽的对象,该槽已经初始化为一个 Number

数组可以被透明包装

与其他内置程序相比,数组可以透明包装。

1
2
3
4
const proxy = new Proxy(new Array(), {})
proxy.push('a')
console.log(proxy.length) // 1
console.log(proxy[0]) // a

之所以数组是可包装的,是因为尽管属性访问是定制的,以使 length 可被访问。数组方法不依赖于内部插槽——它们是通用的

可变通性

作为一种变通方式,我们可以更改处理程序如何转发方法调用,并有选择的设置 this 是指向 target 还是 proxy

1
2
3
4
5
6
7
8
9
10
const handler = {
get(target, propKey, receiver) {
if (propKey === 'getDate') {
return target.getDate.bind(target)
}
return Reflect.get(target, propKey, receiver);
}
}
const proxy = new Proxy(new Date('2020-12-24'), handler);
console.log(proxy.getDate()) // 24

这种方法的缺点是,该方法在此上执行的任何操作都不通过 proxy

Proxy 使用案例

下面用一些例子来展示 Proxy 的使用场景,并在实际应用中熟悉一下 Proxy 的 API

追踪属性访问(get, set)

假设我们有一个方法 tracePropAccess(obj, propKeys) ,每当设置或者获取 obj 中的属性(且该属性存在于 propKeys 数组中),我们便打印一些信息。在下面的代码中,我们将该函数应用于类 Point 的实例。

1
2
3
4
5
6
7
8
9
10
11
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `Point(${this.x}, ${this.y})`;
}
}
let point = new Point(10, 20)
point = tracePropAccess(point, ['x', 'y']);

设置或者获取被追踪的对象 point 会产生如下效果:

1
2
3
4
5
6
> point.x
GET x
5
> point.x = 21
SET x=21
21

有趣的是,无论何时 point 的属性被访问,追踪都将产生效果。因为 this 是始终指向 追踪对象(Proxy)的,而不是 point

ES 5 中,我们可能会像下面这样实现 tracePropAccess(obj, propKeys)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function tracePropAccess(obj, propKeys) {
let propData = Object.create(null)
propKeys.forEach(function (propKey) {
propData[ propKey ] = obj[ propKey ]
Object.defineProperty(obj, propKey, {
get: function() {
console.log('GET '+propKey);
return propData[propKey];
},
set: function(value) {
console.log('SET '+propKey+'='+value);
propData[propKey] = value;
}
})
});
return obj
}

注意,我们正在破坏性地更改原始实现,这意味着我们是元编程。

ES 6 中,我们可以使用基于 Proxy 的解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function tracePropAccess(obj, propKeys) {
const propKeySet = new Set(propKeys)
return new Proxy(obj, {
get(target, propKey, receiver) {
if (propKeySet.has(propKey)) {
console.log('GET '+propKey);
}
return Reflect.get(obj, propKey, receiver)
},
set(target, propKey, value, receiver) {
if (propKeySet.has(propKey)) {
console.log('SET '+propKey+'='+value);
}
return Reflect.set(obj, propKey, value, receiver)
}
})
}

可以看到,我们只是拦截了 getset 方法,并通过 Reflect 将方法转发给 obj 自身的调用,没有去改变 obj 原生方法的调用。

关于未知的警告(get, set)

当涉及到访问属性时,JavaScritp 是比较包容的。例如,当我们访问一个拼写错误或者不存在的属性时,程序将不会抛出异常,而只是返回一个 undefined。因此如果,我们想要到达抛出异常的效果,不妨使用 Proxy 来实现。

1
2
3
4
5
6
7
8
const PropertyChecker = new Proxy({}, {
get(target, propKey, receiver) {
if (!(propKey in target)) {
throw new ReferenceError('Unknown property: ' + propKey);
}
return Reflect.get(target, propKey, receiver);
}
});

让我们使用 PropertyChecker 创建一个对象:

1
2
3
4
5
6
const obj = { 
__proto__: PropertyChecker,
foo: 123
};
obj.foo // 123
obj.fo // ReferenceError: Unknown property: fo

如果我们把 PropertyChecker 编程一个构造函数,那么我们可以在 ES 6 通过继承的方式在对象中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const proxy = new Proxy({}, {
get(target, propKey, receiver) {
if (!(propKey in target)) {
throw new ReferenceError('Unknown property: ' + propKey);
}
return Reflect.get(target, propKey, receiver);
}
});
function PropertyChecker() {}
PropertyChecker.prototype = proxy

class Point extends PropertyChecker {
constructor(x, y) {
super()
this.x = x;
this.y = y;
}
}
const point = new Point(4, 6)
console.log(point.x) // 4
console.log(point.z)
// ReferenceError: Unknown property: z

如果你担心对象属性被以外修改,有两个选择:

  • 使用 Proxy 的陷阱方法 set 对对象进行包装拦截
  • 使用 Object.preventExtensions(obj) 来显示的设置 obj 为不可扩展

数组的负值索引

一些数组方法允许通过 -1 引用最后一个元素,通过 -2 引用倒数第二个元素,等等,例如:

1
2
> ['a', 'b', 'c'].slice(-1)
[ 'c' ]

但是,当我们直接使用方括号([])语法访问数组下标时,就没办法达到上述效果了。那么,我们可以使用 Proxy 来给数组添加这个能力。下面的 createArray() 创建的数组将支持负索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function createArray(...elements) {
const handler = {
get(target, propKey, receiver) {
const index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
const target = [];
target.push(...elements);
return new Proxy(target, handler);
}
const arr = createArray('a', 'b', 'c');
console.log(arr[-1]); // c

数据绑定(set)

数据绑定是一种对象之间数据保持同步的方式。一个流行的用例是基于 MVC 模式的视图数据更新: 使用数据绑定,如果更改模型Model(视图可视化的数据),视图将自动更新数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createObserverArray(callback) {
const array = []
return new Proxy(array, {
set(target, propKey, value, receiver) {
callback(propKey, value);
return Reflect.set(target, propKey, value, receiver);
}
})
}
const observedArray = createObserverArray(
(key, value) => console.log(`arr[ ${key} ] = ${value}`));
observedArray.push('a');
observedArray.push('b');
// arr[ 0 ] = a
// arr[ length ] = 1
// arr[ 1 ] = b
// arr[ length ] = 2

访问基于 rest 风格的 web 服务

可以使用代理创建一个对象,在该对象上可以调用任意方法。在下面的示例中,函数 createWebService 就创建了这样的 service 对象。调用 service 上的方法检索具有相同名称的web服务资源的内容。检索结果是通过 ES 6Promise 来处理的

1
2
3
4
5
const service = createWebService('http://example.com/data');
service.employees().then(json => {
const employees = JSON.parse(json);
···
});

下面我们看下相对复杂的 ES 5 的实现方式,在没有使用代理的情况下,为了事先定义好调用方法,我们需要传入一个包含调用方法名的 propKeys 数组,以保证 createWebService 创建出来的对象是存在调用的方法的。

1
2
3
4
5
6
7
8
9
function createWebService(baseUrl, propKeys) {
const service = {};
propKeys.forEach(function (propKey) {
service[propKey] = function () {
return httpGet(baseUrl + '/' + propKey);
};
});
return service;
}

那么在 ES 6 中,我们通过使用 Proxy 来使实现变得更加简单(因为拦截了 get 方法的缘故,我们无需关心对象调用的方法是否存在,都将将会执行相应请求):

1
2
3
4
5
6
7
function createWebService(baseUrl) {
return new Proxy({}, {
get(target, propKey, receiver) {
return () => httpGet(baseUrl + '/' + propKey);
}
});
}

这两个实现都使用以下函数来发出 HTTP GET 请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function httpGet(url) {
return new Promise(
(resolve, reject) => {
const request = new XMLHttpRequest();
Object.assign(request, {
onload() {
if (this.status === 200) {
// Success
resolve(this.response);
} else {
// Something went wrong (404 etc.)
reject(new Error(this.statusText));
}
},
onerror() {
reject(new Error(
'XMLHttpRequest Error: ' + this.statusText));
}
});
request.open('GET', url);
request.send();
});
}

可撤销引用

可撤销引用的工作方式如下:

客户端不允许直接访问重要资源(对象),只能通过引用(中间对象,资源的包装器)。通常,作用于引用的每个操作都被转发到资源。客户端完成后,通过撤销引用(通过关闭引用)来保护资源。从此以后,对引用执行操作将抛出异常,而不再转发任何内容。

下面例子中,我们为资源创建一个可撤销的引用。然后,通过引用读取资源的一个属性。是能够正常访问到的,因为这时的引用是允许我们访问的。接下来,我们撤销(revoke)引用。现在引用不再让我们访问属性了。

1
2
3
4
5
6
7
const resource = { x: 11, y: 8 }
const { refrence, revoke } = createRevocableReference(resource);
// 允许访问
console.log(refrence.x);
revoke();
// 禁止访问
console.log(refrence.x);

Proxy非常适合实现可撤销引用,因为它可以拦截和转发操作。这是createRevocableReference的一个简单的基于代理的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createRevocableReference(target) {
let enabled = true;
return {
refrence: new Proxy(target, {
get(target, propKey, receiver) {
if (!enabled) {
throw new TypeError('Revoked');
}
return Reflect.get(target, propKey, receiver);
},
has(target, propKey) {
if (!enabled) {
throw new TypeError('Revoked');
}
return Reflect.has(target, propKey)
}
// ...
}),
revoke() {
enabled = false
}
}
}

可以通过前一节中的代理处理程序(proxy-as-handler)技术简化代码。这一次,处理程序基本上是 Reflect 对象。因此,get 陷阱通常返回相应的 Reflect 方法。如果引用已被撤销,则会抛出类型错误。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createRevocableReference2(target) {
let enabled = true;
const handler = new Proxy({}, {
get(dummyTarget, trapName, receiver) {
if (!enabled) {
throw new TypeError('Revoked')
}
console.log(trapName)
return Reflect[trapName]
}
})
return {
refrence: new Proxy(target, handler),
revoke() {
enabled = false;
}
}
}

然而,我们不必自己实现可撤销引用,因为ECMAScript 6允许我们创建可撤销(revocable)的proxy。这一次,撤销发生在代理proxy中,而不是在处理程序handler中。处理程序所要做的就是将每个操作转发给目标。正如我们所看到的,如果处理程序不实现任何陷阱,就会自动发生这种情况。

1
2
3
4
5
6
7
8
function createRevocableReference3(target) {
const handler = {}; // 转发所有的方法
const { proxy, revoke } = Proxy.revocable(target, handler);
return {
refrence: proxy,
revoke
}
}

Proxy 的全部API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface ProxyHandler<T extends object> {
getPrototypeOf? (target: T): object | null;
setPrototypeOf? (target: T, v: any): boolean;
isExtensible? (target: T): boolean;
preventExtensions? (target: T): boolean;
getOwnPropertyDescriptor? (target: T, p: PropertyKey): PropertyDescriptor | undefined;
has? (target: T, p: PropertyKey): boolean;
get? (target: T, p: PropertyKey, receiver: any): any;
set? (target: T, p: PropertyKey, value: any, receiver: any): boolean;
deleteProperty? (target: T, p: PropertyKey): boolean;
defineProperty? (target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean;
enumerate? (target: T): PropertyKey[];
ownKeys? (target: T): PropertyKey[];
apply? (target: T, thisArg: any, argArray?: any): any;
construct? (target: T, argArray: any, newTarget?: any): object;
}

interface ProxyConstructor {
revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };
new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}
declare var Proxy: ProxyConstructor;