每天一点ES6(13):Proxy 和 Reflect
写在前面
又好久没更了,这次来讲的是 ES6 中比较冷门的一块内容:元编程。大部分时候其实我们不会接触到这一块,日常的业务开发通常不会涉及这么高级的操作,但元编程对于一门语言来说可谓是瑞士军刀,一旦掌握,便可以施展出强大的法力。
元编程是个什么鬼?
一般我们编程都是为了实现一些业务上的需求,直接使用语言原生提供的内容即可,最多在其上做一层简单的封装。而元编程则是对编程语言本身进行编程,去修改其原本的行为,实现一些原生方法做不到的事,但实际编写起来又是原生的语法,非常神奇。
Proxy
Proxy 本意是代理,一般我们在计算机网络中经常听到这个词。使用 Proxy 我们可以通过编程来接管外界对对象的操作,改变其原本的行为。有点类似于中间件的概念。
var obj = new Proxy({}, {
get: function (target, key, receiver) {
console.log(`getting ${key}!`);
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
console.log(`setting ${key}!`);
return Reflect.set(target, key, value, receiver);
}
});
obj.count = 1
// setting count!
++obj.count
// getting count!
// setting count!
// 2
如上的代码就对对象 obj 的读取和设置操作进行了接管,在其原有行为之前加上了一些内容。
Proxy 的基本使用为: const proxy = new Proxy(target, handler)
,target 为被代理的对象,handler 中定义了拦截的行为。
Proxy 实例可以作为对象的原型:
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
let obj = Object.create(proxy);
obj.time // 35
这里 obj 本身并没有 time 属性,因此沿原型链向上寻找,找到 proxy 的 time 属性,proxy 虽然也没有 time 属性,但其拦截了所有属性访问操作并固定返回 35,因此依然能拿到结果。
Proxy 支持的拦截操作
操作 | 作用 |
---|---|
get(target, key, receiver) | 拦截对象属性的读取,三个参数分别为:目标对象、属性名、proxy 实例本身(即 this 所指),其中最后一个参数可选 |
set(target, key, value, receiver) | 拦截对象属性的设置,返回布尔值 |
has(target, key) | 拦截 key in obj 的操作,返回布尔值 |
deleteProperty(target, key) | 拦截 delete obj[key] 操作,返回一个布尔值 |
ownKeys(target) | 拦截 Object.getOwnPropertyNames(obj) 、 Object.getOwnPropertySymbols(obj) 、 Object.keys(obj) 操作,返回一个数组,包含目标对象所有自身的属性的属性名。 Object.keys() 的返回结果仅包括目标对象自身的可遍历属性。 |
getOwnPropertyDescriptor(target, key) | 拦截 Object.getOwnPropertyDescriptor(obj, key) ,返回属性的描述对象 |
defineProperty(target, key) | 拦截 Object.defineProperty(obj, key, desc) 、 Object.defineProperties(obj, key, descs) ,返回一个布尔值 |
preventExtensions(target) | 拦截 Object.preventExtensions(obj) ,返回一个布尔值 |
getPrototypeOf(target) | 拦截 Object.getPrototypeOf(obj) ,返回一个对象 |
isExtensible(target) | 拦截 Object.isExtensible(obj) ,返回一个布尔值 |
setPrototypeOf(target, prototype) | 拦截 Object.setPrototypeOf(obj, prototype) ,返回一个布尔值,如果目标对象是函数那么还有两种额外操作可以拦截。 |
apply(target, obj, args) | 拦截 Proxy 实例作为函数调用的操作,例如: proxy(...args) 、 proxy.call(object, ...args) 、 proxy.apply(...) |
construct(target, args) | 拦截 Proxy 实例作为构造函数调用的操作,例如: new proxy(...args) |
Proxy.revocable()
Proxy.revocable()
返回一个对象,该对象的 proxy
属性是 Proxy 实例, revoke
是一个用于取消 Proxy 实例的函数。被取消的 Proxy 实例不可再被访问。
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 中的 this
在使用了 Proxy 的情况下,目标对象内部的 this
关键字会指向 Proxy 代理,而非原本的对象。使用时要时刻注意这一点,Proxy 内部的 this
未必是我们以为的那一个
Reflect
字面意思解叫“反射”,熟悉 Java 的朋友应该也有接触过这个词。它和 Proxy 一样,都是 ES6 引入的用于操作对象的新 API。
Reflect 的设计目的有以下几个:
- 将
Object
中一些明显属于语言内部的方法放到Reflect
对象上,例如:Object.defineProperty
。 - 合理化一些
Object
方法的返回结果,例如:Object.defineProperty
在无法定义属性是会抛出错误,而Reflect.defineProperty
只是返回false
。 - 让
Object
中命令式的操作都变成函数式。例如:key in obj
、delete obj[key]
是命令式的,Reflect.has(obj, key)
、Reflect.deleteProperty(obj, key)
就是函数式的。 Reflect
中的方法与Proxy 中的一一对应,在Proxy
中始终可以通过Reflect
获取默认行为。
简而言之, Reflect
的存在意义有两点:一是把一些原本捆绑在 Object
对象上但实际上更应该属于语言内部的方法提取出来,并加以合理化,让 Object
更纯粹作为一种数据结构。二是和 Proxy
组 CP,用于在 Proxy
中始终指向默认行为,防止加了代理之后回不去。
Reflect 的静态方法
如前面所说的, Reflect
的静态方法和 Proxy
支持的拦截操作一一对应。
- Reflect.apply(target, thisArg, args)
- Reflect.construct(target, args)
- Reflect.get(target, name, receiver)
- Reflect.set(target, name, value, receiver)
- Reflect.defineProperty(target, name, desc)
- Reflect.deleteProperty(target, name)
- Reflect.has(target, name)
- Reflect.ownKeys(target)
- Reflect.isExtensible(target)
- Reflect.preventExtensions(target)
- Reflect.getOwnPropertyDescriptor(target, name)
- Reflect.getPrototypeOf(target)
- Reflect.setPrototypeOf(target, prototype)
小结
通过 Proxy 和 Reflect,我们可以进一步控制对象的行为,甚至改变其默认行为。这可以说是对 JavaScript 能力的又一次提升。
该系列的其他文章
- 每天一点ES6(19):Module
- 每天一点ES6(18):Class
- 每天一点ES6(17):Async / Await
- 每天一点ES6(16):Generator
- 每天一点ES6(15):Promise
- 每天一点ES6(14):Iterator
- 每天一点ES6(12):Set 和 Map
- 每天一点ES6(11):Symbol
- 每天一点ES6(10):对象的扩展
- 每天一点ES6(9):函数的扩展
- 每天一点ES6(8):数组的扩展
- 每天一点ES6(7):数值的扩展
- 每天一点ES6(6):正则的扩展
- 每天一点ES6(5):字符串的扩展
- 每天一点ES6(4):Babel
- 每天一点ES6(3):解构赋值
- 每天一点ES6(2):let 和 const
- 每天一点ES6(1):概述