I'll try to explain one more time.
The language has inconsistencies, yes of course, but the inconsistencies are not in
Proxy. You raise some yourself:
- There are 3 "type" of objects: plain object, function and array. The 2 former can be identified with
typeof (and a
null check), the latter only with
Array.isArray (I will ignore legacy
TypedArray instances are not in fact of the "array" type, but they do both have exotic index named properties. And even there they differ, with TypedArray having "integer index" (up to 2^53 - 1), and arrays having "array index" (up to 2^32 - 2). TypedArray instances have a prototype accessor for a
length property, where all array objects have an own
length non-configurable property.
The language has also invariants that make it possible for an author to reason about its program. Some examples:
- If an object claims to be an array, a function, or a plain object, then any later observation of its type will continue to be same
- If an own property of an object is observed as non configurable, that property will keep existing for every future access
- If an object claims to be non-extensible, its own property list will not grow or shrink in the future
Proxy and language invariants
Now what you seem to advocate for is for Proxy to have full programmatic control of what can be observed about it. However Proxy like any other object should not be allowed to break the language invariants. There would be 2 approaches to enforcing the invariants:
- have the JS engine remember every response that the proxy trap gives, and error if a new result is inconsistent with a previous one. I hope you can spot the complexity this would create
- defer the invariants check to a "model" object, assuming that the model object itself respects the language invariants. This is the kind of recursive logic proof where if the environment only has well behaved objects (as the specification is written), then no misbehaved objects can exist.
Enforcing language invariants, whichever approach is taken, puts limits on the programmatic behavior of Proxies. For simplicity reasons, the "model" object approach was chosen to enforce the language invariants. The
target of a
Proxy is that "model" object, and it has no other purpose as far as the language is concerned.
Capabilities and Limitations of Proxy
A Proxy instance can claim to be whatever object type it wants, emulating whatever behavior it wants, as long as its consistent with the language invariants. If it wants to transparently claim it's an array or a function, it simply can by using the appropriate object kind as model
target at construction.
The limits come when APIs (userland and some built-ins) do equality or brand checks on objects. For performance and encapsulation reasons, Proxies are not allowed to trap for equality checks, or private field (and spec internal slots) lookups. That means that if an API checks if a proxy instance exists in a WeakSet, it will not match its target. Or that a call like
Object.getOwnPropertyDescriptor(Uint8Array.__proto__.prototype, Symbol.toStringTag).get.call(proxyInstance) will not let the proxy pass the brand check for
Specific concerns raised
It doesn't conceptually do anything different between function and array. It simply defers to the
It doesn't really read inside, it's just that
Array.isArray is the API to determine that an object is of type array. The object type of a proxy is determined at instantiation based on the
ownKeys can return whatever properties it wants, as long as it's consistent with language invariants, enforced through the
target "model" object. Since it's impossible to create an object of array kind without an own
length non-configurable property, a proxy is not able to create such an object either.
It depends. It's possible to build membranes that will correctly pass
instanceof checks. The problem is that
instanceof does not involve a single object, but multiple, and ask them if they're logically related. In some cases, you basically have to have the membrane wrap both operands of the
ECMAScript is actually pretty consistent. It does not perform proxy piercing checks on arguments.
Array.isArray is different/special as it's conceptually the same as
typeof. Furthermore you can think of it as your
Proxy adopting the type of the
target at construction, and
Array.isArray reporting that type.
What would it mean to call or construct something that does not have the "function" type?