# The Array.isArray shenanigans

**URL:** <https://es.discourse.group/t/the-array-isarray-shenanigans/1745>\
**Category:** Spec Reading\
**Tags:** proxy\
**Created:** [June 24, 2023, 12:39pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745 "2023-06-24T12:39:26Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 12:39pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/1 "2023-06-24T12:39:26Z")

</div>

I have a library (not worth a mention for this thread sake) that forward any Proxy trap to another realm and it was my surprise to realize that `Array.isArray(ref)` returns always `true` or always `false` if that Proxied value is either an array or an object.

As a result, I had to monkey patch `Array.isArray` in the thread global scope because the proxy simply forward information to other threads (the main in this case) but even if these are `new Number(123)` and all details to forward such information is there, the `Array.isArray(Proxy(Number))` would return true because the proxy uses a `type` / `value` tuple to define that reference ... let's say `['number', 1]` for discussion sake.

## Missing

There is no trap or `Reflect` method to decide when an `Array.isArray(ref)` brand check happens, but such method infer the type out of the blue.

If I use an object `{type, value}` instead as proxied value, any brand check for `Array` will return false even if the referenced value in my thread **is** an array.

This has caused a lot of issues in using all proxy traps that survive my code but won't survive 3rd party libraries using `Array.isArray(ref)` to do their logic, so that at the end a (not so) obtrusive monkey-patch of the native function in the thread my code works into too was needed to fix 3rd party libraries just using plain JS and fix my library behavior around these 3rd party libraries expectations.

Can anyone please understand what I am saying and tell me why any "exotic proxy reference" should just return true when the whole point of proxies is that you _should_ never know what you deal with, until you really deal with it?

Thank you.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [June 24, 2023, 2:49pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/2 "2023-06-24T14:49:13Z")

</div>

A Proxy instance behaves like it's target. `Array.isArray` will return the value as if applied to the target. Similarly, `typeof proxy === 'function'` if the target is a callable, which you cannot runtime patch.

If you want your proxy instance to behave like aplain object, just use a plain object as the target. The target object does not need to be the same as the object in the other realm onto which you apply the traps. Do note however that the object invariants must be fulfilled on the target, so for example if `isExtensible` returns false, the target object must be non extensible.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 3:08pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/3 "2023-06-24T15:08:56Z")

</div>

> [@mhofman](#):
>
> Similarly, `typeof proxy === 'function'`

not at all ... `typeof []` is `"object"` so that was indeed a very bad example and (imho) it underlines my point further ... beside `typeof` that can't indeed be mimicked via a `Proxy`, why there's no way to define the result of native `Array.isArray` out there?

I can mimic all other `new Boolean` or similar wrappers, I can't tell a _Proxy_ the held value is **not** an array ...

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 3:14pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/4 "2023-06-24T15:14:10Z")

</div>

> [@mhofman](#):
>
> If you want your proxy instance to behave like aplain object

Maybe I wasn't clear ... I want my proxy "whatever" to behave like "whatever" is held ... I can have numbers, boolean, TypedArray, string references (as objects) and there's no way I can use the same common "struct" to represent them all ... for `function` I can bind their "struct" into a real function but `typeof` indeed give me `"object"` for all other wrappers, the rest is primitive so it works just fine (out of a proxy).

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 3:20pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/5 "2023-06-24T15:20:41Z")

</div>

if adding anything, `Array.isArray` over a non-meant-to-be arrays is a detail **leak** the `Proxy` offers today to side-channel libraries that would like to use either a `[type, value]` underlying reference, or a `{type. value}` one ... so this somehow concerns me further, as both are `typeof` `"object"` but `isArray` current check will reveal their real nature, while any other usage of that _proxied_ value wouldn't (`instanceof` to start with).

```javascript
const thing = new Proxy([], {getPrototypeOf: () => Object.prototype});
if (
  typeof thing === 'object' &&
  Array.isArray(thing) &&
  !(thing instanceof Array)
) {
  console.log('what the heck is this?');
}

```

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [June 24, 2023, 3:24pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/6 "2023-06-24T15:24:58Z")

</div>

Proxy isn’t a generic replacement for every kind of thing - it won’t work with things that have private fields or internal slots either.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 3:33pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/7 "2023-06-24T15:33:56Z")

</div>

> [@ljharb](#):
>
> Proxy isn’t a generic replacement for every kind of thing

I rise this example one more time ...

```javascript
const thing = new Proxy([], {getPrototypeOf: () => Object.prototype});
if (
  typeof thing === 'object' &&
  Array.isArray(thing) &&
  !(thing instanceof Array)
) {
  console.log('what the heck is this?');
}

```

how is anyone out there able to understand what a proxied value is when all the odds are in place?

---

<div class="post-metadata">

**Author:** ![Josh-Cena](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/josh-cena/32/1408_2.png) [@Josh-Cena](https://es.discourse.group/u/Josh-Cena)\
**Post date:** [June 24, 2023, 3:36pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/8 "2023-06-24T15:36:02Z")

</div>

You don't even need a proxy to get a what-the-heck object; in general, any branded-check is orthogonal to `instanceof`, because the former uses a private property.

```js
const thing = [];
Object.setPrototypeOf(thing, Object.prototype);
if (
  typeof thing === 'object' &&
  Array.isArray(thing) &&
  !(thing instanceof Array)
) {
  console.log('what the heck is this?');
}

```

However, I believe `Array.isArray` drilling through proxies is indeed a special case.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 3:41pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/9 "2023-06-24T15:41:12Z")

</div>

> [@Josh-Cena](#):
>
> However, I believe `Array.isArray` drilling through proxies is indeed a special case.

or is it? how is any non `Array` object able to satisfy a primitive / widely used check, as `Array.isArray(ref)` is?

Not only the proxy drilling is concerning, without any trap to tell otherwise, the `Array.isArray` spec looks like the most under-specd thing I could find in latest ECMAScript ... it throws a `true` to anything without even thinking about it, and while I am sure it can't be changed now that it's doomed by default, at least please let's add a new _Proxy_ trap behavior to avoid people patching `Array.isArray` all over the inevitably proxied world that both _WASM_ and _Worker_ offer, thank you!

---

<div class="post-metadata">

**Author:** ![Josh-Cena](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/josh-cena/32/1408_2.png) [@Josh-Cena](https://es.discourse.group/u/Josh-Cena)\
**Post date:** [June 24, 2023, 3:44pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/10 "2023-06-24T15:44:23Z")

</div>

> [@WebReflection](#):
>
> how is any non `Array` object able to satisfy a primitive / widely used check, as `Array.isArray(ref)` is?

That is what I mean by "special case"—you cannot replicate this with user objects :) I don't think the committee is going to pursue this behavior in the future either, but legacy behavior can't be changed.

> it throws a `true` to anything without even thinking about it

It returns `true` for exactly two things: an array (whatever realm it comes from), and a proxy for an array. I'm not sure what surprising behavior can be caused by that, other than a possible leak-of-abstraction.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 3:48pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/11 "2023-06-24T15:48:05Z")

</div>

the leak of abstraction is what any _WASM_ or _Worker_ would have but the fact `Array.isArray(ref)` an then `ref.map(fail)` is an hazard with current specs ... you provided an "evil" example to prove my point but my point is that I am proxying a reference and I _want_ exclusive handling over that reference.

Every single (weird exception a part but not part of this thread) operation works out of the box but then `Array.isArray(ref)` is able, in a single check, to branch differently every single library that should not understand, reveal, or deal with, **my** proxied references there for a well defined purpose.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 3:52pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/12 "2023-06-24T15:52:15Z")

</div>

In case anyone is wondering, `return new Proxy(Array.isArray(thing) ? thing : {thing})` is not an answer for the very same reason I am concerned in here: people currently need an `Array.isArray` monkey patch to avoid this _Proxy_ shenanigan and nobody can ensure that `Array.isArray` call is not tainted already out there (or it won't need to be patched to satisfy results) ... screwing developers intent in the making.

Can we please think about a trap for this check which is AFAIK the only one that drills proxies beside `apply` and `construct` that need something different from a `typeof "object"` to work?

Thank You!

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [June 24, 2023, 5:16pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/13 "2023-06-24T17:16:36Z")

</div>

The topic is moving way too fast for me, but I think the gist of the surprise comes from the fact `Array.isArray` does a brand check (aka checks the presence / value of an internal slot).

It's the only static method to do so in the standard library. It is indeed a legacy decision, and since then the guideline has been to only do brand checking on `this`, not on method arguments (static or otherwise).

That said that behavior is consistent with proxied functions, in the sense that the `typeof` operator does a brand check as well for callable objects.

The motivation is to allow building faithful membranes, so I am a little confused by how you find this behavior surprising since it seems you're trying to use proxies for a similar use case.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [June 24, 2023, 5:25pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/14 "2023-06-24T17:25:27Z")

</div>

> [@WebReflection](#):
>
> the fact `Array.isArray(ref)` an then `ref.map(fail)` is an hazard

I don't understand this. It is the job of your proxy handlers traps to also proxy the `map` property. The above only fails if your proxy fails to do its job.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [June 24, 2023, 5:31pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/15 "2023-06-24T17:31:42Z")

</div>

> [@WebReflection](#):
>
> avoid people patching `Array.isArray` all over the inevitably proxied world that both _WASM_ and _Worker_ offer

People have building membrane libraries for over a decade, and none have had to patch Array.isArray.

There are quite a few large production systems that do just what you claim requires patching Array.isArray, and they don't have to do it. I'm not sure where the misunderstanding is, but I would read more into public membrane implementations to find out.

---

<div class="post-metadata">

**Author:** ![mhofman](https://avatars.discourse-cdn.com/v4/letter/m/f14d63/32.png) [@mhofman](https://es.discourse.group/u/mhofman)\
**Post date:** [June 24, 2023, 5:36pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/16 "2023-06-24T17:36:45Z")

</div>

> [@WebReflection](#):
>
> can use the same common "struct" to represent them all

Well you can, but not as the target, which is special and captures/represents the "kind" and invariants of the proxy instance.

You can use a closure or a WeakMap to keep your data associated with the proxy instance and its actual target.

---

<div class="post-metadata">

**Author:** ![ljharb](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/ljharb/32/8_2.png) [@ljharb](https://es.discourse.group/u/ljharb)\
**Post date:** [June 24, 2023, 6:10pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/17 "2023-06-24T18:10:35Z")

</div>

It could be an array from another realm, or it could be a subclass of Array that had its [[Prototype]] set to something that doesn't extend Array - or, it could be a Proxy to an array whose traps make it not instanceof Array.

Again, you can't generically wrap all JS values in a Proxy - you _always_ have to branch based on what it is, or, restrict yourself to a subset of values, in order to imitate the thing properly (and in many cases, it's impossible unless you also patch builtins).

Array.isArray is special indeed, in that it and typeof are the only things that pierce through a Proxy.

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 7:17pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/18 "2023-06-24T19:17:12Z")

</div>

can anyone show me a single code base where after an `Array.isArray(ref)` there is also a `ref instance of Array` brand check?

---

<div class="post-metadata">

**Author:** ![WebReflection](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/webreflection/32/248_2.png) [@WebReflection](https://es.discourse.group/u/WebReflection)\
**Post date:** [June 24, 2023, 7:20pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/19 "2023-06-24T19:20:59Z")

</div>

@mhofman I am not answering you as you are assuming I don't know what I am doing, what I am talking about, and why this issue was filed in the first place ... but you should stop assuming that from people raising issues in here, and the "nobody in 10 years had issues with it" narrative is extremely lame as I get paid to do stuff nobody did before, and quite successfully to date, so please try to be less dismissive and more open minded, thanks.

**edit** yes, nobody in the last 10 years did anything I am doing, and working, today, so thanks for asking, I don't want to make this thread a personal library goal on purpose, as I've mentioned in the first post, otherwise you'd be amused by what I'm doing and why this is a real-world issue out there.

---

<div class="post-metadata">

**Author:** ![bakkot](https://yyz2.discourse-cdn.com/free1/user_avatar/es.discourse.group/bakkot/32/22_2.png) [@bakkot](https://es.discourse.group/u/bakkot)\
**Post date:** [June 24, 2023, 7:44pm UTC](https://es.discourse.group/t/the-array-isarray-shenanigans/1745/20 "2023-06-24T19:44:39Z")

</div>

I agree that `isArray`'s behavior is weird. But I really don't think it warrants a new trap.

Proxies already have certain other restrictions on which behaviors they are allowed to customize - the `typeof` example is already mentioned, but there's also, for example, the [[[IsExtensible]]](https://tc39.es/ecma262/multipage/ordinary-and-exotic-objects-behaviours.html#sec-proxy-object-internal-methods-and-internal-slots-isextensible) trap, which is required to return the same value as that trap on the underlying object.

So there's always certain things that you need to know when establishing your Proxy - you can't blindly make a Proxy from an arbitrary target and then try to customize its behavior to act completely like something else after-the-fact. You really do need to figure out certain details of what you're going to be pretending to be when you set up the Proxy. "Whether you are going to be pretending to be an array" is only one example among several of things you need to know up front.

(You mentioned that `new Proxy(Array.isArray(thing) ? thing : {thing})` wouldn't work for you, but I don't understand why - all you said is that `isArray` might be patched, but of course `new Proxy` might be patched; you generally can't be defensive against earlier-running code patching stuff.)

[Next page](https://es.discourse.group/t/the-array-isarray-shenanigans/1745.md?page=2)
