Proxy
proxy可以理解成对对象的某些操作进行一次拦截。
1 | var obj = new Proxy({}, { |
对target进行设置代理时,如果handler没有进行任何拦截,则等同于直接通向源对象。
1 | var target = {}; |
1 | var target = {}; |
注意,要使得Proxy起作用,必须针对Proxy实例(上例是proxy对象)进行操作,而不是针对目标对象(上例是空对象)进行操作。
一个拦截器可以拦截多个操作。
1 | var handler = { |
Proxy实例方法
get()
上面基本上也列出了很多get方面的用例,下面加一个链式调用的例子。
1 | var pipe = (function () { |
set()
同get类似,可以通过设定拦截器防止用户对某些属性进行读写。也可以对属性的赋值做一些规范。
1 | var handler = { |
apply()
负责拦截函数的调用、call
和apply
操作。
1 | var handler = { |
三个参数的含义分别为:
target: 目标对象
ctx: 目标对象的上下文
args: 目标对象的参数数组
1 | var david = { |
has()
负责隐藏某些属性,不被in
操作符发现。
1 | var obj = { a: 10 }; |
注意,这里的has()必须返回一个布尔值类型。
The has method must return a boolean value.
1 | var obj = { |
这里因为对于_name属性来说,拦截器返回的是"not found"
字符串,所以是true
。如果改成返回空字符串,就会返回false
。
1 | var obj = { |
construct()
拦截new
命令。同has()
一样,返回值必须要求为对象类型,否则抛出错误。
1 | var handler = { |
deleteProperty()
用于拦截delete
操作,如果抛出错误或者返回false
,则无法被删除。
1 | var handler = { |
defineProperty()
拦截Object.defineProperty
操作。对于返回值,文档中说明:
The defineProperty method must return a Boolean indicating whether it has successfully defined the property on the target or not.
1 | var handler = { |
enumerate()
用来拦截for...in
循环的。返回一个iterator
对象。
1 | var handler = { |
不过以后应该要避免定义这项拦截器,毕竟根据mozilla文档,这个特性在ES7中会被废弃。目前mozilla浏览器似乎已经停止使用。不过在firefox浏览器中测试上面这段代码还是可以用的。
getOwnPropertyDescriptor()
拦截Object.getOwnPropertyDescriptor
,返回一个属性描述对象或者undefined
。
1 | var handler = { |
经测试,返回值不能是一个普通的对象,必须包含属性描述内容,{ value: 'tar', writable: true, enumerable: true, configurable: true }
,缺一项或者完全不符合都会抛出错误。
Uncaught TypeError: 'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property 'wat' which is either non-existant or configurable in the proxy target
1 | var handler = { |
getPrototypeOf()
用来拦截Object.getPrototypeOf
运算符以及下列操作。
Object.prototype.__proto__
Object.prototype.isPrototypeOf()
Object.getPrototypeOf()
Reflect.getPrototypeOf()
instanceof
必须返回一个对象或者null,不会对对象进行检查。
1 | var proto = {}; |
isExtensible()
拦截Object.isExtensible
操作。这里有一个强制要求:
Object.isExtensible(proxy) === Object.isExtensible(target)
1 | var p = new Proxy({}, { |
ownKeys()
拦截Object.keys()
操作。必须返回一个可枚举的对象。
1 | var target = {}; |
这里因为对对象不会做细致检查,因此返回普通对象时会解析成空数组,即使是一个类数组对象。
1 | var target = {}; |
setPrototypeOf()
拦截Object.setPrototypeOf
,或者obj.__proto__
操作。
1 | var handler = { |
Proxy.revocable()
返回一个可取消的Proxy实例。方法返回值是一个对象,包括{proxy: proxy, revoke: revoke}
。
proxy: 取消函数相关的Proxy实例
revoke: 取消函数
一旦revoke被调用,proxy对象即被取消,所有作用在proxy上的拦截器均被取消,若再调用直接报错。
1 | let target = {}; |
Reflect
Reflect
对象是一个内置对象,不可构建(使用new
操作符),所有属性和方法都是静态的。
至于Reflect
对象的功能是什么,ES6官方文档上并没有说明,不过我在stackoverflow上找到了一个答案。
总结下来三点:
目前基本方法都绑在`Object`对象上,为了做向下兼容,目前不会删除这些方法,但基本上将来都会移到`Reflect`对象下。
使用在Proxy中,防止原生方法被Proxy拦截污染,并方便快捷链接到目标对象的对应方法上。
防止全局Object对象被设置拦截。
不过还有一点需要注意到,同样的方法,比如Object.defineProperty
和Reflect.defineProperty
,两者的返回值是有区别的。
Object.defineProperty会返回目标对象或者抛出异常。
Reflect.defineProperty会返回`true`或`false`表示是否成功。
相信对于js开发者来说,第二种返回值更加人性,毕竟我们不想各个地方都要提防着是否需要try...catch
。而且使用上更加便捷,可以作为if的条件来使用。
另外,Reflect
可以代替一些基本操作。
Reflect.deleteProperty(obj, prop) <=> delete obj[prop]
Reflect.has(obj, prop) <=> (prop in obj)
注意下段代码就能看出区别:
1 | var david = {name: "david"} |
因此可以看出,调用Reflect[method]
是最安全的,不会担心会不会有拦截器作用。