We should consider the possibility of extending the Proxy Handler API such that handlers get access to the proxy for which they are currently intercepting. Motivating use cases:
Proxy.create. It also cannot reliably distinguish whether it is servicing an object or a function proxy. When given access to a proxy, the handler could perform Object.getPrototypeOf(proxy), typeof proxy and proxy instanceof Fun to get at this data (credit goes to David Bruant)getPropertyDescriptor and getPropertyNames traps would have access to the proxy, they can easily be turned into derived traps, as indicated in this strawman.The easiest way to allow a handler access to the proxy it is currently servicing is to pass the proxy as an additional argument to the handler traps. From here, there are multiple routes to take:
proxy as an optional last argument to all traps.proxy as an argument at the most appropriate position for each trap.proxy only as an argument to the getPropertyDescriptor and getPropertyNames traps (for the purpose of defining their derived behavior).
We could add a proxy parameter as an optional last argument to all existing traps.
Pro:
Con:
Object.getOwnPropertyDescriptor takes an extra argument in a later edition, how can we reconcile this with existing code that assumes that the second parameter is the proxy? (requires refactoring)
For example, the trap getOwnPropertyDescriptor is triggered by code like:
Object.getOwnPropertyDescriptor(proxy, name)
Yet the order in which the params are passed to the trap is reversed:
getOwnPropertyDescriptor: function(name, proxy) {...}
Passing proxy as the last argument is odd in this way for get{Own}PropertyDescriptor, defineProperty, delete, hasOwn. It is OK for get{Own}PropertyNames, keys, fix (freeze/seal/preventExtensions), has, enumerate. The get and set traps already have implicit access to proxy via receiver (which is either the proxy or an object inheriting from the proxy). The proxy argument could be passed either as an extra first argument or as an extra last argument.
We could add a proxy parameter as an extra argument to all existing traps. Depending on the trap, the argument is added either as a mandatory argument or as an optional trailing argument.
Pro:
proxy is consistent with its position in the intercepted code.Con:
proxy parameter.
Below is a proposed updated API (when the proxy parameter is optional, it is enclosed in square brackets):
// fundamental traps getOwnPropertyDescriptor: function(proxy, name) -> PropertyDescriptor | undefined // Object.getOwnPropertyDescriptor(proxy, name) getOwnPropertyNames: function([proxy]) -> [ string ] // Object.getOwnPropertyNames(proxy) defineProperty: function(proxy, name, propertyDescriptor) -> any // Object.defineProperty(proxy,name,pd) delete: function(proxy, name) -> boolean // delete proxy.name fix: function([proxy]) -> { string: PropertyDescriptor } // Object.{freeze|seal|preventExtensions}(proxy) | undefined // derived traps getPropertyDescriptor: function(proxy, name) -> PropertyDescriptor | undefined // Object.getPropertyDescriptor(proxy, name) (not in ES5) getPropertyNames: function([proxy]) -> [ string ] // Object.getPropertyNames(proxy) (not in ES5) has: function(name, [proxy]) -> boolean // name in proxy hasOwn: function(proxy, name) -> boolean // ({}).hasOwnProperty.call(proxy, name) get: function(receiver, name, [proxy]) -> any // receiver.name set: function(receiver, name, val, [proxy]) -> boolean // receiver.name = val enumerate: function([proxy]) -> [string] // for (name in proxy) (return array of enumerable own and inherited properties) keys: function([proxy]) -> [string] // Object.keys(proxy) (return array of enumerable own properties only)
Other ways to decide on the optionality of proxy:
proxy mandatory for the traps that trap methods on Object, and optional (trailing) for all others.
Only add the proxy parameter to the getPropertyDescriptor and getPropertyNames traps. Pro: keeps the overall API simple while still allowing derived behavior for these traps. Con: inconsistent, doesn’t cater to all motivating use cases.
— Tom Van Cutsem 2011/02/28 06:10
getPropertyDescriptor and getPropertyNames traps).Discussed at the March 2011 TC39 meeting.
Q: Why not consider proxy as an instance variable of the handler? A: Would preclude sharing a single handler among multiple proxies, or else handler would have to keep track of all the proxies it is serving as part of its instance state.
Q: why not bind a handler’s this to the proxy it is serving? A: confuses meta-levels (this = handler = meta-level, proxy = base-level). Also, makes handler inheritance unworkable: can inherit from a handler, in which case this in a parent handler should refer to the inheriting handler.
General agreement that we may want to provide the proxy as an argument to all traps.
Andreas: experimenting with DOM wrappers. All prototype-climbing traps require access to the receiver object (which is not necessarily the proxy object), not just the get/set traps. The get and set trap may want access to both the receiver and the proxy.
Tom: propose to add proxy as a first argument to all traps. Andreas, Brendan, Dave: in favor of adding it as an optional last argument:
proxy parameterproxy parameter is dangerous (runaway recursion hazard), good to be able to ignore it most of the timeCurrent consensus:
receiver as a first argument to all prototype-climbing traps (get, set, has, getPropertyNames, getPropertyDescriptor traps)— Tom Van Cutsem 2011/03/30 12:54