This article is translated into Chinese. The original English text is linked to jakearchibald.com/…

Allen Wirfs-Brock failed to find the principle of array.isarray (obj) on Twitter and found the wrong answer.

Array type check

function foo(obj) {
  / /...
}
Copy the code

Suppose obj is an array, and do something special to it. Example json.stringify makes arrays special.

We can do this:

if (obj.constructor == Array) / /...
Copy the code

But this does not determine the derivation of the array:

class SpecialArray extends Array {}
const specialArray = new SpecialArray();
console.log(specialArray.constructor === Array); // false
console.log(specialArray.constructor === SpecialArray); // true
Copy the code

If you want to determine subclasses you can use instanceof:

console.log(specialArray instanceof Array); // true
console.log(specialArray instanceof SpecialArray); // true
Copy the code

But things get complicated when multiple Realms are introduced.

Multiple realms

Realm contains the JavaScript global object that Self references, so you can say that the program runs in different realms on different pages. This is also true in iframes, where the ECMAScript agent is shared among same-origin Iframes, meaning that objects can propagate across realms.

Take a good look:

<iframe srcdoc=""></iframe>
<script>
  const iframe = document.querySelector('iframe');
  const arr = iframe.contentWindow.arr;
  console.log(arr.constructor === Array); // false
  console.log(arr.constructor instanceof Array); // false
</script>
Copy the code

Both are false because:

console.log(Array === iframe.contentWindow.Array); // false
Copy the code

. Iframe has its own array constructor, which is different from the parent page.

The inputArray.isArray

console.log(Array.isArray(arr)); // true
Copy the code

Array.isArray will always return true when judging arrays, even if they are created in other realms. It will always return true whether they are derived from arrays or from other realms. This is what json.stringify uses.

However, as Allen reveals, this does not mean that the ARR has any array methods. Some or all of the methods may be set to undefined, or even remove the entire stereotype of the array:

const noProtoArray = [];
Object.setPrototypeOf(noProtoArray, null);
console.log(noProtoArray.map); // undefined
console.log(noProtoArray instanceof Array); // false
console.log(Array.isArray(noProtoArray)); // true
Copy the code

This is where I made a mistake in Allen’s poll. I chose ‘it has Array methods’, which is the answer with the least choice. So it’s kind of hip now.

Anyway, if you want to defend against the above problem, you can use array methods from array prototypes:

if (Array.isArray(noProtoArray)) {
  const mappedArray = Array.prototype.map.call(noProtoArray, callback);
  / /...
}
Copy the code

Symbols and realms

Look at this:

<iframe srcdoc=""></iframe>
<script>
  const iframe = document.querySelector('iframe');
  const arr = iframe.contentWindow.arr;

  for (const item of arr) {
    console.log(item);
  }
</script>
Copy the code

It will print out 1, 2, 3. But the for-of loop calls arR [symbol.iterator] when it works, which is why it works across realms:

const iframe = document.querySelector('iframe');
const iframeWindow = iframe.contentWindow;
console.log(Symbol === iframeWindow.Symbol); // false
console.log(Symbol.iterator === iframeWindow.Symbol.iterator); // true
Copy the code

Although each realm has its own Symbol object, Symblo. Iterator is the same from realm to realm.

To paraphrase Keith Cirkel: Symbols is the most and least unique thing in JavaScript.

The most unique

const symbolOne = Symbol('foo');
const symbolTwo = Symbol('foo');
console.log(symbolOne === symbolTwo); // false
const obj = {};
obj[symbolOne] = 'hello';
console.log(obj[symbolTwo]); // undefined
console.log(obj[symbolOne]); // 'hello'
Copy the code

The string argument in the Symbol function is just a description, even though these symbols are unique within the same realm.

The least unique

const symbolOne = Symbol.for('foo');
const symbolTwo = Symbol.for('foo');
console.log(symbolOne === symbolTwo); // true
const obj = {};
obj[symbolOne] = 'hello';
console.log(obj[symbolTwo]); // 'hello'
Copy the code

Symbol.for(STR) creates a unique Symbol bound to the string you pass. The interesting thing is that it gets the same in different realms.

const iframe = document.querySelector('iframe');
const iframeWindow = iframe.contentWindow;
console.log(Symbol.for('foo') === iframeWindow.Symbol.for('foo')); // true
Copy the code

This explains how symbol. iterator works.

Create your ownisfunction

If we want to create an IS function that works in different realms, we can do so through Symbol.

const typeSymbol = Symbol.for('whatever-type-symbol');

class Whatever {
  static isWhatever(obj) {
    return obj && Boolean(obj[typeSymbol]);
  }
  constructor() {
    this[typeSymbol] = true; }}const whatever = new Whatever();
Whatever.isWhatever(whatever); // true
Copy the code

Instances from other Realms, subclasses of that instance, and instances that remove stereotypes can use this method.

The only minor problem is that you have to manually ensure that the Symbol name is unique in all code. IsWhatever may fail if someone else creates symbol. for(‘ what-type-symbol ‘) and gives it other meanings.

(Original addressjuejin.cn/post/686…, reprint must be approved by the author!

Further reading

  • Iterators
  • Async iterators
  • Keith Cirkel’s deep dive into symbols