I'll try to explain one more time.
Language inconsistencies
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 document.all
here)
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.
Language Invariants
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 TypedArray
.
Specific concerns raised
It doesn't conceptually do anything different between function and array. It simply defers to the target
model object to determine the observable type of the proxy. It just happens that in JavaScript, the way to check for array or for function is not the same.
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 target
.
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 instanceof
operator.
That is because Web APIs tend to perform brand checks (using slots) on arguments that proxies cannot trap. It's a design decision of the Web. On the JavaScript side, APIs only perform brand checks on the receiver, not on arguments, and use regular properties of the arguments that can be trapped by proxies.
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 typeof
or Array.isArray
reporting that type.
What would it mean to call or construct something that does not have the "function" type?