3

I was going through the polyfill of Array.isArray method on MDN, and this is what I found:

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}

While this works, I wonder why MDN hasn't listed the following as the polyfill for isArray?

if (!Array.isArray) {
  Array.isArray = function(arg) {
    return arg.constructor === Array;
  };
}

Above code is a bit short and simpler. I wonder if there is any advantage of using MDN's implementation as compared to above one?

darKnight
  • 5,651
  • 13
  • 47
  • 87
  • 2
    If you get an object from an iframe, for example it's going to be *an* array but it will not be created from *your current* `Array` constructor, so that check will fail. – VLAZ May 28 '20 at 20:50
  • @VLAZ: Didn't quite get that. Why should an object/Array from an iFrame be any different? Can you demonstrate with code what you are trying to say? – darKnight May 28 '20 at 20:52
  • 1
    It's an array but comes from a different environment. The constructor function is a *different instance* of `Array`. And as we know `function() {} === function() {}` is false. – VLAZ May 28 '20 at 20:53
  • Does this answer your question? [Difference between using Array.isArray and instanceof Array](https://stackoverflow.com/questions/22289727/difference-between-using-array-isarray-and-instanceof-array) – VLAZ May 28 '20 at 20:58
  • `instanceof Array` is pretty much compatible with your suggested `== Array`. The same problems arise - it fails cross-environment, as you'd be trying to compare different instances of `Array`. [This answer](https://stackoverflow.com/a/22289982/) talks about the problem directly. – VLAZ May 28 '20 at 21:00
  • @VLAZ: This will be true if iFrame uses a separate thread of JS. But I couldn't find any evidence that this is implemented in browsers as of today.. – darKnight May 31 '20 at 13:33
  • Why would threads be at all relevant? Two objects are never equal unless they are the same object. [Array constructors are different](https://jsbin.com/bayituvesa/1/edit?html,js,console). I've never found any evidence that two windows will share the same array constructor - regardless of threads. – VLAZ May 31 '20 at 14:13
  • Your code proves the point. But I still don't understand why an `Array` constructor from an `iFrame` is another `Array` object? If the iFrame is also using the same JavaScript thread, then why are there two different instances of `Array`? And this seems to be true for all JS constructors like `String`, `Object`, etc.. – darKnight May 31 '20 at 14:24
  • Every different window gets a different basic constructors. There isn't a single global Array constructor shared everywhere. – VLAZ May 31 '20 at 14:52

2 Answers2

5

The most important reason that makes the MDN polyfill a safe one to use is that an array is an exotic object. The best way to differentiate an exotic object would be through the use of an exotic feature related to that object – that is to say, an exotic property of the exotic object.

If you look at the specification of the Object.prototype.toString method, you will find that it uses the abstract operation isArray, which checks for exotic array objects.

On the other hand look at the specification of the constructor property. It is not an exotic property of the array, and so javascript code can change it easily.

const x = [];
x.constructor = Object

In fact, the constructor property is more intended for meta programming. You could – in es5 – create subclasses without touching the constructor property.

Now, here are things that can go wrong with your implementation:

const customIsArray = function(arg) {
    return arg.constructor === Array;
};

// 1) won't work with subclasses of Array

class CustomArray extends Array {
    // ...
}

const customArray = new CustomArray()
customIsArray(customArray) // false
Array.isArray(customArray) // true

// 2) won't work across different realms (iframes, web workers, service workers ... if we are speaking about the browser environment)

const iframe = document.createElement('iframe')
document.body.appendChild(iframe)
const IframeArray = iframe.contentWindow.Array;
const iframeArray = new IframeArray();
customIsArray(iframeArray) // false
Array.isArray(iframeArray) // true

// 3) won't work with few edge cases like (customIsArray will return true, while Array.isArray will return false)

const fakeArray1 = { __proto__: Array.prototype }
const fakeArray2 = { constructor: Array }

customIsArray(fakeArray1) // true
Array.isArray(fakeArray1) // false

customIsArray(fakeArray2) // true
Array.isArray(fakeArray2) // false 

Codesmith
  • 5,779
  • 5
  • 38
  • 50
ehab
  • 7,162
  • 1
  • 25
  • 30
0

One problem would be that a non-array object with a constructor property of Array would pass when it shouldn't:

// Bad polyfill, do not use
Array.isArray = function(arg) {
  return arg.constructor === Array;
};

const badObj = {};
badObj.constructor = Array;
console.log(Array.isArray(badObj));

The above would be a very strange situation, but a polyfill must be as spec-compliant as possible, even if it requires more convoluted code.

CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • 1
    Couldn't you also add a `toString()` method that returns `[object Array]` and misleads the MDN polyfill? – Barmar May 28 '20 at 20:46
  • 3
    It's probably not possible to make it deal with code that's deliberately trying to mislead it. – Barmar May 28 '20 at 20:46
  • You could, but if the built-in methods are monkeypatched to be inaccurate, all bets are off the table anyway. Many otherwise spec-compliant polyfills could be broken that way. – CertainPerformance May 28 '20 at 20:47
  • 1
    @Barmar yes, you could. But MDN's polyfill doesn't use `badObj`'s `toString`, it uses `Object.prototype.toString`. I guess someone _could_ deliberately mess up the standard objects, but that's a somewhat bizarre corner case you can guard yourself against. – mbojko May 28 '20 at 20:52