从 ECMAScript 2015 开始,JavaScript 获得了 Proxy
和 Reflect
对象的支持,允许拦截并定义基本语言操作的自定义行为(例如,属性查找,赋值,枚举,函数调用等)。借助这两个对象,可以在 JavaScript 元级别进行编程。
元编程(Metaprogramming)是一种编程技术,其中计算机程序能够将其他程序视为其数据。这意味着一个程序可以被设计为读取、生成、分析或转换其他程序,甚至在运行时修改自身。在某些情况下,这允许程序员最大限度地减少表达解决方案的代码行数,从而减少开发时间。它还使程序具有更大的灵活性,可以有效地处理新情况而无需重新编译。
Proxy
Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改。Proxy
可以理解成,在目标对象之前架设一层"拦截",外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
js
new Proxy(target, handler)
target
:被代理的对象,Proxy
会对target
对象进行包装。它可以是任何类型的对象,包括内置的数组,函数甚至是另一个代理对象。handler
:被代理对象上的自定义行为,它是一个对象,它的属性提供了某些操作发生时所对应的处理函数。
句柄和陷阱
-
handler.apply()
方法是[[Call]]
对象内部方法的陷阱,供函数调用等操作使用。该陷阱可以拦截以下操作:- 函数调用:
proxy(...args)
Function.prototype.apply()
和Function.prototype.call()
Reflect.apply()
- 调用
[[Call]]
内部方法的任何其他操作
- 函数调用:
-
handler.construct()
方法是[[Construct]]
对象内部方法的陷阱,由new
运算符等操作使用。为了使new
操作在生成的Proxy
对象上有效,用于初始化代理的目标本身必须是有效的构造函数。construct
方法必须返回一个对象。该陷阱可以拦截以下操作:new
运算符:new myFunction(...args)
Reflect.construct()
- 调用
[[Construct]]
内部方法的任何其他操作。
-
handler.defineProperty()
方法是[[DefineOwnProperty]]
对象内部方法的陷阱,由Object.defineProperty()
等操作使用。该陷阱可以拦截以下操作:Object.defineProperty()
,Object.defineProperties()
Reflect.defineProperty()
- 调用
[[DefineOwnProperty]]
内部方法的任何其他操作。
-
handler.deleteProperty()
方法是[[Delete]]
对象内部方法的陷阱,由delete
运算符等操作使用。该陷阱可以拦截以下操作:delete
运算符:delete proxy[prop]
和delete proxy.prop
Reflect.deleteProperty()
- 调用
[[Delete]]
内部方法的任何其他操作。
-
handler.get()
方法是[[Get]]
对象内部方法的陷阱,由属性访问器等操作使用。该陷阱可以拦截以下操作:- 属性访问:
proxy[prop]
和proxy.prop
Reflect.get()
- 调用
[[Get]]
内部方法的任何其他操作。
- 属性访问:
-
handler.getOwnPropertyDescriptor()
方法是[[GetOwnProperty]]
对象内部方法的陷阱,由Object.getOwnPropertyDescriptor()
等操作使用。该陷阱可以拦截以下操作:Object.getOwnPropertyDescriptor()
Reflect.getOwnPropertyDescriptor()
- 调用
[[GetOwnProperty]]
内部方法的任何其他操作。
-
handler.getPrototypeOf()
方法是[[GetPrototypeOf]]
对象内部方法的陷阱,由Object.getPrototypeOf()
等操作使用。该陷阱可以拦截以下操作:Object.getPrototypeOf()
Reflect.getPrototypeOf()
__proto__
Object.prototype.isPrototypeOf()
instanceof
- 调用
[[GetPrototypeOf]]
内部方法的任何其他操作。
-
handler.has()
方法是[[HasProperty]]
对象内部方法的陷阱,由in
运算符等操作使用。该陷阱可以拦截以下操作:in
运算符:prop in proxy
with
检查:with(proxy) { (prop); }
Reflect.has()
- 调用
[[HasProperty]]
内部方法的任何其他操作。
-
handler.isExtensible()
方法是[[IsExtensible]]
对象内部方法的陷阱,由Object.isExtensible()
等操作使用。该陷阱可以拦截以下操作:Object.isExtensible()
Reflect.isExtensible()
- 调用
[[IsExtensible]]
内部方法的任何其他操作。
-
handler.ownKeys()
方法是[[OwnPropertyKeys]]
对象内部方法的陷阱,由Object.keys()
、Reflect.ownKeys()
等操作使用。该陷阱可以拦截以下操作:Object.getOwnPropertyNames()
Object.getOwnPropertySymbols()
Object.keys()
Reflect.ownKeys()
- 调用
[[OwnPropertyKeys]]
内部方法的任何其他操作。
-
handler.preventExtensions()
方法是[[PreventExtensions]]
对象内部方法的陷阱,由Object.preventExtensions()
等操作使用。该陷阱可以拦截以下操作:Object.preventExtensions()
Reflect.preventExtensions()
Object.seal()
Object.freeze()
- 调用
[[PreventExtensions]]
内部方法的任何其他操作。
-
handler.set()
方法是[[Set]]
对象内部方法的陷阱,该方法由诸如使用属性访问器设置属性值之类的操作使用。该陷阱可以拦截以下操作:- 属性赋值:
proxy[prop] = value
和proxy.prop = value
Reflect.set()
- 调用
[[Set]]
内部方法的任何其他操作。
- 属性赋值:
-
handler.setPrototypeOf()
方法是[[SetPrototypeOf]]
对象内部方法的陷阱,由Object.setPrototypeOf()
等操作使用。该陷阱可以拦截以下操作:Object.setPrototypeOf()
Reflect.setPrototypeOf()
- 调用
[[SetPrototypeOf]]
内部方法的任何其他操作。
撤销 Proxy
Proxy.revocable()
方法被用来创建可撤销的 Proxy
对象。这意味着 proxy
可以通过 revoke
函数来撤销,并且关闭代理。此后,代理上的任意的操作都会导致 TypeError
。
js
Proxy.revocable(target, handler)
Reflect
Reflect
是一个内置对象,它提供了可拦截 JavaScript 操作的方法。该方法和 Proxy handler
类似,但 Reflect
方法并不是一个函数对象。
Reflect.apply(target, thisArgument, argumentsList)
对一个函数进行调用操作,同时可以传入一个数组作为调用参数。和Function.prototype.apply()
功能类似。Reflect.construct(target, argumentsList[, newTarget])
对构造函数进行new
操作,相当于执行new target(...args)
。Reflect.defineProperty(target, propertyKey, attributes)
和Object.defineProperty()
类似。如果设置成功就会返回true
。Reflect.deleteProperty(target, propertyKey)
作为函数的delete
操作符,相当于执行delete target[name]
。Reflect.get(target, propertyKey[, receiver])
获取对象身上某个属性的值,类似于target[name]
。Reflect.getOwnPropertyDescriptor(target, propertyKey)
类似于Object.getOwnPropertyDescriptor()
。如果对象中存在该属性,则返回对应的属性描述符,否则返回undefined
。Reflect.getPrototypeOf(target)
类似于Object.getPrototypeOf()
。Reflect.has(target, propertyKey)
判断一个对象是否存在某个属性,和in
运算符 的功能完全相同。Reflect.isExtensible(target)
类似于Object.isExtensible()
。Reflect.ownKeys(target)
返回一个包含所有自身属性(不包含继承属性)的数组。(类似于Object.keys()
, 但不会受enumerable
影响).Reflect.preventExtensions(target)
类似于Object.preventExtensions()
。返回一个Boolean
。Reflect.set(target, propertyKey, value[, receiver])
将值分配给属性的函数。返回一个Boolean
,如果更新成功,则返回true
。Reflect.setPrototypeOf(target, prototype)
设置对象原型的函数。返回一个Boolean
,如果更新成功,则返回true
。
Reflect
有助于将默认操作从处理程序转发到目标。
- 将
Object
对象的一些明显属于语言内部的方法(比如Object.defineProperty
),放到Reflect
对象上。现阶段,某些方法同时在Object
和Reflect
对象上部署,未来的新方法将只部署在Reflect
对象上。也就是说,从Reflect
对象上可以拿到语言内部的方法。 - 修改某些
Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。 - 让
Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。 Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,总可以在Reflect
上获取默认行为。