ECMAScript2015开始,JavaScript新增了ProxyReflect对象.通过这两个对象你可以拦截并自定义语言原来的操作行为(例如:属性查找、赋值、枚举、函数调用等).借助这两个对象你可以在JavaScript进行元级别进行编程(元编程).

Proxy

Proxy对象用于定义基本操作的自定义行为(如属性查找、赋值、枚举、函数调用等).

1
2
3
4
5
6
7
8
let p = new Proxy(target, handler);
// target 需要被包装的目标对象(可以是任何类型的对象,包括原生数组,函数,甚至另一个Proxy)

// handler 对象是一个占位符对象,它包含Proxy的捕获器.其属性是当执行一个操作时定义代理的行为的函数
// 开发者可以在 handler中自定义JS对象中13种行为
// handler必须是个Object,否则JS引擎会抛 TypeError错误

// p 是返回一个新对象

ProxydefineProperty,写法上很类似.defineProperty不能对数组进行包装,并且它只有[setter]和[getter]两种对象行为自定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let handler = {
get: function(target, name){
return name in target ? target[name] : 37;
}
};

let p = new Proxy({}, handler);

p.a = 1;
p.b = undefined;

console.log(p.a, p.b); // 1, undefined

console.log('c' in p, p.c); // false, 37


// 无代理
let target = {};
let p = new Proxy(target, {});

p.a = 37; // 操作转发到目标

console.log(target.a); // 37. 操作已经被正确地转发

截屏2019-09-1415.10.13.png

handler

方法 拦截方法 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.
handler.defineProperty() Object.defineProperty()
Reflect.defineProperty()
handler.has() in符的使用:foo in proxy
Object.create(proxy)
Reflect.has()
handler.get() 获取对象属性值
Reflect.set()
handler.set() 给对象属性赋值
Reflect.set()
handler.deleteProperty() 删除对象属性delete obj.foo
Reflect.deleteProperty()
如果目标属性在对象是不可配置属性(configurable is false),则该属性不能删除
handler.ownKeys() Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
返回值是个列表
列表中的值是字符串类型或Symbol类型
列表包含对象所有不可配置的属性
如果目标对象不可以扩展,则列表只含有它自身的属性,不会含有其他属性
handler.apply() proxy(..args)
Function.prototype.apply() and Function.prototype.call()
Reflect.apply()
handler.apply()不包含有不变量
handler.construct() Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
结果一定是一个Object

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
let products = new Proxy({
browsers: ['Internet Explorer', 'Netscape']
},
{
get: function(obj, prop) {
// 附加属性
if (prop === 'latestBrowser') {
return obj.browsers[obj.browsers.length - 1];
}

// 缺省行为是返回属性值
return obj[prop];
},
set: function(obj, prop, value) {
// 附加属性
if (prop === 'latestBrowser') {
obj.browsers.push(value);
return;
}

// 如果不是数组则进行转换
if (typeof value === 'string') {
value = [value];
}

// 缺省行为是保存属性值
obj[prop] = value;
}
});

console.log(products.browsers); // ['Internet Explorer', 'Netscape']
products.browsers = 'Firefox'; // ?传入一个 string (错误地)
console.log(products.browsers); // ['Firefox'] <- ?没问题, ?得到的是一个 array

products.latestBrowser = 'Chrome';
console.log(products.browsers); // ['Firefox', 'Chrome']
console.log(products.latestBrowser); // 'Chrome'

Proxy.revocable(target, handler)

Proxy.revocable(target, handler) 方法可以用来创建一个可撤销的代理对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
var {proxy, revoke} = Proxy.revocable({}, {
get: function(target, name) {
return "[[" + name + "]]";
}
});
console.log(proxy.foo); // "[[foo]]"

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

Reflect是一个内置的对象,它提供拦截 JavaScript 操作的方法。这些方法和Proxy中handler方法一模一样的.都是有十三种handler方法.

Reflect不是构造函数,不能在用new运算符也不能作为函数调用它.Reflect的所有方法和属性都是静态的.

Reflect存在的几个目的:

  1. Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。现阶段,某些方法同时在ObjectReflect对象上部署,未来的新方法将只部署在Reflect对象上。也就是说,从Reflect对象上可以拿到语言内部的方法。
  2. 修改某些Object方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
  3. Object操作都变成函数行为。某些Object操作是命令式,比如name in objdelete obj[name],而Reflect.has(obj, name)Reflect.deleteProperty(obj, name)让它们变成了函数行为。
  4. Reflect对象的方法与Proxy对象的方法一一对应,只要是Proxy对象的方法,就能在Reflect对象上找到对应的方法。这就让Proxy对象可以方便地调用对应的Reflect方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy怎么修改默认行为,你总可以在Reflect上获取默认行为。

方法

截屏2019-09-1415.11.47.png

参考

https://tc39.es/ecma262/#sec-reflection

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Meta_programming

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect

http://es6.ruanyifeng.com/#docs/proxy

http://es6.ruanyifeng.com/#docs/reflect