
从 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.propReflect.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 proxywith检查: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上获取默认行为。