This Proxy API replaces the earlier proxies API. A summary of the major differences between this API and the previous API can be found on the old strawman page.
To construct a proxy that wraps a given target
object:
var proxy = Proxy(target, handler);
Both target
and handler
must be proper Objects. It is not necessary to use new
to create new proxy objects.
The handler
is an object that may implement the following API (name denotes a property name, → is followed by return type, [t] means array-of-t, etc.):
{ getOwnPropertyDescriptor: function(target,name) -> desc | undefined // Object.getOwnPropertyDescriptor(proxy,name) getOwnPropertyNames: function(target) -> [ string ] // Object.getOwnPropertyNames(proxy) getPrototypeOf: function(target) -> any // Object.getPrototypeOf(proxy) defineProperty: function(target,name, desc) -> boolean // Object.defineProperty(proxy,name,desc) deleteProperty: function(target,name) -> boolean // delete proxy[name] freeze: function(target) -> boolean // Object.freeze(proxy) seal: function(target) -> boolean // Object.seal(proxy) preventExtensions: function(target) -> boolean // Object.preventExtensions(proxy) isFrozen: function(target) -> boolean // Object.isFrozen(proxy) isSealed: function(target) -> boolean // Object.isSealed(proxy) isExtensible: function(target) -> boolean // Object.isExtensible(proxy) has: function(target,name) -> boolean // name in proxy hasOwn: function(target,name) -> boolean // ({}).hasOwnProperty.call(proxy,name) get: function(target,name,receiver) -> any // receiver[name] set: function(target,name,val,receiver) -> boolean // receiver[name] = val enumerate: function(target) -> iterator // for (name in proxy) (iterator should yield all enumerable own and inherited properties) keys: function(target) -> [string] // Object.keys(proxy) (return array of enumerable own properties only) apply: function(target,thisArg,args) -> any // proxy(...args) construct: function(target,args) -> any // new proxy(...args) }
Each of the above methods is called a “trap”. The first argument to each trap is a reference to the target
object wrapped by the proxy that triggered the trap. The comment behind each trap signature shows example code that may trigger the trap.
All traps are optional. If missing (more precisely, if handler[trapName]
returns undefined
), the proxy defaults to forwarding the intercepted operation to its target
object.
Non-interceptable operations Some operations are insensitive to proxies, and are implicitly applied to the proxy’s target
instead:
typeof proxy // equivalent to typeof target proxy instanceof F // equivalent to target instanceof F
Identity A proxy has its own identity, which is distinct from its target. Hence proxy !== target
and for any WeakMap wm
, wm.get(proxy)
is not necessarily equal to wm.get(target)
.
Functions The apply
and construct
traps are triggered only if typeof target === “function”
. Otherwise, trying to call or construct a proxy fails just like when trying to call/construct a non-function object.
Note the role of the 2nd thisArg
argument to the apply
trap:
var fun = function(){}; var proxy = Proxy(fun, handler); proxy(...args); // triggers handler.apply(fun, undefined, args) var obj = { m: proxy }; obj.m(...args); // triggers handler.apply(fun, obj, args) Function.prototype.apply.call(proxy, thisArg, args); // triggers handler.apply(fun,thisArg,args)
A proxy may wrap irregular objects such as functions, arrays, Date objects and host objects. In such cases, a proxy acquires any internal properties (other than the standard internal properties for regular Objects) from its wrapped target.
Some standard internal properties on regular Objects are always shared with the wrapped target: this includes the [[Class]] internal property:
Object.prototype.toString.call(proxy)
uses the target
‘s [[Class]] in the result stringtypeof proxy
is equal to typeof target
.
A proxy does not have a [[Prototype]] internal property. Prototype access should instead trigger the getPrototypeOf
trap, and check if the return value of that trap is consistent with the [[Prototype]] value of the proxy’s [[Target]] (see invariant enforcement, below).
A proxy does not have an [[Extensible]] internal property. Accessing this internal property should instead trigger the isExtensible
trap, and check if the return value of that trap is consistent with the [[Extensible]] value of the proxy’s [[Target]] (see invariant enforcement, below).
Date Example:
var d = new Date(); var p = Proxy(d, {}); Object.prototype.toString.call(p) // "[object Date]" Object.getPrototypeOf(p) // Date.prototype typeof p // "object" p === d // false
Function When the wrapped target
is a Function:
Object.prototype.toString.call(proxy)
returns “[object Function]”
Function.prototype.toString.call(proxy)
returns Function.prototype.toString.call(target)
Function.prototype.apply.call(proxy, rcvr, args)
triggers handler.apply(target, rcvr, args)
Function.prototype.call.call(proxy, rcvr, ...args)
triggers handler.apply(target, rcvr, args)
Function.prototype.bind.call(proxy, rcvr, ...args)
returns a currying of the proxy
The [[Call]] and [[Construct]] behavior of a proxy p
is governed by the following rule:
typeof target === “function”
, then so is typeof p
and calling/constructing the proxy always traps the apply/construct
trap.typeof target !== “function”
, then so is typeof p
and any attempt to call/construct p
raises a TypeError
as usual, stating that p
is not a function. The apply
or construct
traps are never invoked.This upholds the constraints that:
typeof
is stable: it depends only on typeof target
, not on the presence or absence of the apply
or construct
traps.typeof obj === “function”
.
The second restriction could be revisited if there exist host objects that are not typeof “function”
but that are callable/constructable.
Array
var target = []; var p = Proxy(target, handler); Object.prototype.toString.call(p) // "[object Array]" Object.getPrototypeOf(p) // Array.prototype typeof p // "object" Array.isArray(p) // true p[0] // triggers handler.get(target, "0", p)
Since this Proxy API requires one to pass an existing object as a target
to wrap, it may seem that this API precludes the creation of fully “virtual” objects that are not represented by an existing JSObject. It’s easy to create such “virtual” proxies: just pass a fresh empty object as the target to Proxy
and implement all the handler traps so that none of them defaults to forwarding, or otherwise touches the target
.
As long as the virtual proxy doesn’t expose non-configurable properties or becomes non-extensible, the target object is fully ignored (except to acquire internal properties such as the target’s [[Class]]).
To accomodate such virtual object abstractions, and to keep these abstractions in sync with future editions of the spec, we propose built-in library support in the form of a virtual object api.
Direct proxies are made accessible from a new "@reflect" module. This module also contains “helper” functions that correspond one-to-one to each of the trap methods of the handler API. They enable Proxy handlers to conveniently forward an operation to their target object. These methods can also be useful for general reflection use.
Proxies may be used as prototypes of other objects, e.g. by calling Object.create(proxy)
. When a proxy is used as a prototype, some of its traps may get triggered not because the proxy itself was “touched” by external code, but rather an object that inherits (directly or indirectly) from the proxy:
var proxy = Proxy(target, handler); var child = Object.create(proxy); child[name] // triggers handler.get(target,name,child) child[name] = val // triggers handler.set(target,name,val,child) name in child // triggers handler.has(target,name) for (var prop in child) {...} // triggers handler.enumerate(target)
Note that the get
and set
traps get access to the child
on which the original property access or assignment took place. This is in contrast to an “own” property access, which gets passed a reference to the proxy itself:
var proxy = Proxy(target, handler); proxy[name] // triggers handler.get(target,name,proxy)
A proxy ensures that its handler and its target do not contradict each other as far as non-configurability and non-extensibility are concerned. That is: the direct proxy inspects the return values from the handler traps, and checks whether these return values make sense given the current state of its target. If the handler and the target are always consistent, then the enforcement will have no observable effects. Otherwise, upon detection of an invariant violation, the proxy will throw a TypeError
.
Below is a list of invariants for Objects based on the ES5 spec.
Definitions:
desc1
and desc2
for a property name
are incompatible if desc1 = Object.getOwnPropertyDescriptor(target,name)
and Object.defineProperty(target,name,desc2)
would throw a TypeError
.getOwnPropertyDescriptor
defineProperty
getOwnPropertyNames
deleteProperty
getPrototypeOf
freeze | seal | preventExtensions
isFrozen | isSealed | isExtensible
hasOwn
has
get
get
attribute is undefined, check whether the trap result is also undefined.set
set
attribute is not undefinedkeys
enumerate
More information about these invariants can be found in the header documentation of the prototype implementation.
Revocable proxies are proxies whose target
-link can be nulled out, allowing for the target (and the handler) to become eligible for garbage-collection. Revocable proxies are created by calling the factory method Proxy.revocable
:
let { proxy, revoke } = Proxy.revocable(target, handler); proxy.foo // traps as usual revoke() // revokes the proxy, always returns undefined proxy.foo // throws TypeError: "proxy is revoked"
Proxy.revocable(target, handler)
returns an object {proxy: proxy, revoke: revoke}
.revoke
is a zero-argument function that, when called, revokes its associated proxy.TypeError
unconditionally.See the proxies spec.
* Removed ''iterate()'' trap as iterators can be defined on any old object via an ''iterate'' unique name. See discussion at [[harmony:iterators]]. A proxy will intercept the request for its iterator via the ''get'' trap, which is passed the unique ''iterator'' name as argument.
enumerate()
trap return an iterator rather than an array of strings. To retain the benefits of an iterator (no need to store collection in memory), we might need to waive the duplicate properties check. Resolution: accepted (duplicate properties check is waived in favor of iterator return type)Proxy
to control interaction with private names.proto and proxies
getPrototypeOf
trap. This would simplify membranes. Writable __proto__ already destroys the invariant that the [[Prototype]] link is stable. Engines already need to accomodate.Object.getPrototypeOf(proxy) // ⇒ handler.getPrototypeOf(target)
getPrototypeOf
trap.setPrototypeOf
trap? Depends on how we end up specifying __proto__: if as special data prop, there won’t be an observable capability to set prototypes on objects. If as an accessor, then the __proto__ setter applied to a proxy would trigger such a trap. We could also poison the setter, at which point there is no need for setPrototypeOf
.get
trap (similar for set
). The handler gets to decide whether this property name is magical or not.trapping instanceof
instanceof
trap? See earlier strawman for how this could work. Does giving the RHS function access to the LHS instance consistute a serious capability leak?x instanceof Proxy(t,h) ⇒ h.hasInstance(t,x)
instanceof
as sending a message to the RHS, passing LHS as argument (explicit capability grant rather than a “leak”).trapping isSealed and friends
Object.{isExtensible, isSealed, isFrozen}
trappable for direct proxies. This would again simplify membranes (makes it possible to maintain the extensibility state of the wrapped target across a membrane). Invariant enforcement would check whether the return value of these traps is consistent with the state of the target.nativeCall trap
Date.prototype.getTime.call(Proxy(aDate, handler)) ⇒ aDate.getTime()
). This seems fine as long as such built-ins don’t return an object but just a primitive value. Date.prototype.getYear.call(Proxy(t, h)) ⇒ h.nativeCall(t, Date.prototype.getYear, [])
Date.prototype
methods.Proxies and private names
proxy[name]
triggers handler.getName(target, name.public)
trap[ name, value ]
: by returning the name object, handler can “prove” to the proxy that it indeed knows about the private name, and can provide the corresponding valueundefined
: handler signals to proxy “I don’t know about this private name, please forward to the target”VirtualHandler
defaultValue
defaultValue
trap that intercepts internal invocations to the built-in [[DefaultValue]] method on Proxies, or do we want to expose it via a privately named property, or just inherit the default Object behavior for Proxies? Sentiment at the July TC39 meeting was against adding a trap – with value objects we’d want more choices than number and string as return types.