Yes, when creating a proxy, you must use the appropriate object which you are trying to emulate. If you want your object to have callable behavior, the target must be a callable. If you want your object to pass Array.isArray
checks, the target object must be an array.
You don't need to change your internal protocol about what information you keep about the "real target", that can be a tuple or a record. However that structure cannot be the target
of the Proxy
instance.
Yes people have done exactly this kind of things before. From what I gather, your library implements a synchronous membrane between agents. There are a quite a few membrane implementations out there, both synchronous and asynchronous, either with attenuations/transformations, or plain passthrough. Implementing a synchronous membrane between agents (Worker) may not have any open source implementation, but I do remember reading about some experiments in that area a couple years ago (some were abusing synchronous XHR and ServiceWorker).
Yes, Proxy
was actually meant to implement membranes, which were also one of the motivations for adding WeakMap
to the language.
Proxy
are meant to intercept any object behavior, and in the case of membranes, reflect it onto a "real target". However objects in JavaScript have invariants. In order for exotic proxy objects to not break the language invariants, Proxy
enforces these invariants on the target
object. As such, the target
of a Proxy
has special constraints, and cannot be any value you want. However the target
object of the proxy does not need to be the same as the "real target" the proxy ultimately intends in the case of membranes. In some cases like cross agent, or even cross network, you obviously cannot have a direct references to real target objects, only serializable identifiers.
Interestingly, the special casing of Array.isArray
to check the target object of proxies was actually to make it possible for membranes to be more transparent, and have the ability to create objects that pass existing array checks. It is consistent and compatible with the Callable check when a target
is a function or constructor.
As I mentioned before, the proxy target is not there just to determine the "type" of proxy objects, but also to enforce the object invariants of the language. One example is that if a property is non-configurable and non-writable, its descriptor cannot change, aka 2 calls to getOwnPropertyDescriptor
must return the same descriptor and in particular the same value
. This is enforced by the proxy trap with the implementation doing a [[GetOwnProperty]]
on the target
object, and compare it to the result returned by the proxy trap.
The observed behavior of proxy and its interaction with Array.isArray
is not a language problem. Although it may be surprising, it is there for a reason, now hopefully fully explained.
The 2 things I did not understand and tried to resolve:
- what your usage of proxy was, and how that deviated from the intended usage by the spec. I did suspect you were mis-using the
target
of the Proxy, but wasn't sure.
- What your proposed new trap should do? And how that would solve the problem you perceived. Let's for example assume there was a
isArray
trap. You would then be able to create an object that has callable semantics and pretends to be an array as well. While not an explicit invariant, that's something the language doesn't allow today, and would wreak havoc on all kinds of code out there. You also cannot move both the callable and array type checks as traps, as too much of the language relies on callable checks not executing user code (including the proxy implementation itself).
I don't believe at any point anyone answered "no". With knowledge of the problem space of proxies and membranes we tried to figure out where the perceived problem may lie, and provide solutions to get the results you seek. In summary:
Array.isArray(new Proxy([])) === true;
Array.isArray(new Proxy({})) === false;
typeof (new Proxy(() => {})) === 'function';
const realTargetInfo = new WeakMap();
const makeProxyFromInfo = (info) => {
const target = makeTargetFromInfo(info); // create an object, array or function
realTargetInfo.set(target, info);
return new Proxy(target, proxyHandler);
};