This page describes an alternative API that gets rid of the distinct function proxies created by Proxy.createFunction. We will refer to the previous API that distinguishes object from function proxies explicitly as the “non-uniform” API and to this alternative as the “uniform” API.
In the uniform API, there is no separate Proxy.createFunction method. Instead, the handler protocol is extended with two additional fundamental traps:
call: function(thisBinding, ...args) -> any // reifies proxy(...args) construct: function(...args) -> object // reifies new proxy(...args)
Additionally, the uniform API allows proxies to virtualize typeof by passing a third (optional) argument to Proxy.create:
var p = Proxy.create(handler, prototype, type);
The type argument could be either “object” (the default), “function” or any string except “undefined”, “boolean”, “number” or “string” (cf. section 11.4.3, The typeof operator).
Using the uniform proxy API, a generic no-op proxy that works for both objects and functions can be created as follows (assuming that handlerMaker also defines call and construct traps):
function wrap(target) { return Proxy.create(handlerMaker(target), Object.getPrototypeOf(target), typeof Object(target)); }
In the non-uniform proposal, function proxies were callable and object proxies were not. But when is a uniform proxy callable? In the ES5 spec, Object values that implement [[Call]] are callable (sec 9.11, IsCallable). One way to define IsCallable on proxies is to “feature test” the handler, as if by evaluating “call” in handler, to test whether it implements the call trap. This is undesirable for two reasons:
has trap in addition to the invoke trap.
Instead, we propose to have IsCallable(aProxy) return true iff the type argument passed to Proxy.create equals “function”.
In the non-uniform API, the [[Prototype]] of function proxies is guaranteed to refer to Function.prototype. This constraint was enforced because in the ES5 spec, every value for which typeof value equals “function” will have Function.prototype as its prototype. Uniform proxies can violate this constraint (e.g. Proxy.create(h, Object.prototype, ‘function’)). If we don’t want uniform proxies to violate this constraint, Proxy.create could perform a sanity check, requiring its prototype argument to be Function.prototype if its type argument equals “function”.
What is the [[Class]] of a uniform proxy? In the non-uniform API, the [[Class]] of a function proxy is “Function”. One possibility is to derive the [[Class]] from the type parameter as follows:
type argument equals “function”, the proxy’s [[Class]] is “Function”
In the uniform API, the fixing protocol becomes more complex. In the non-uniform API, fixing a function proxy generates a function object whose [[Call]] and [[Construct]] methods are fixed to the callTrap and the constructTrap passed as arguments to Proxy.createFunction. For the uniform API, we could make [[Call]] and [[Construct]] refer to the corresponding handler traps, but again this would require the proxy implementation to interact with the handler object by means of an operation other than message sending (in this case, property access, e.g. handler.call), again prohibiting the “double lifting” technique.
We could make the fix trap return three values instead of just a single property map:
fix: function() { var pdMap = ...; var callTrap = ...; var constructTrap = ...; return { properties: pdMap, call: callTrap, construct: constructTrap }; }
The call and construct properties of the return value are only required if the type argument to Proxy.create equals “function”.
In the non-uniform API, Function.prototype.toString.call(aFunctionProxy) could delegate the call to the function proxy’s callTrap. It is not clear what the result should be on uniform proxies.