看到一篇文章Arrays, symbols, and realms,简单搜了一下,关于判断数组的文章基本上从 typeofinstanceofObject.prototype.toString.call(obj) === '[objectg Array]''讲到 isArray就完了。但是它实际上干了什么?

正文

在推上,一个大佬问兄弟们,大家知不知道 Array.isArray(obj) 干了什么,结果显示,并不知道。更重要的是,本大佬也弄错了。

检查 arrays 的类型

function foo(obj){
    // ...
}

假如说我们需要针对 obj 是数组的情况,来专门做一些操作,比如 JSON.stringify,它对于数组的输出,是跟其他对象不同的。

我们可以这样:

if (obj.constructor === Array) // ...

但对于继承于 Array 的数据来说,就不行了:

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

针对子类我们又可以用 instanceof

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

但是当你遇到多个领域的时候就不好使了:

多个领域

一个领域包含了 JavaScript 的全局对象,self 指向的那个。可以这样讲:跑在 worker 里面的代码,和跑在页面里面的代码,是属于不同领域的。iframe 之间也是这样(属于不同领域)。但是,同源的 iframe 们共享一个 ECMAScript agent[啥意思?],意味着对象这个东西可以在不同领域之间传递

<iframe srcdoc="<script>var arr = [];</script>"></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>

都会返回 false,因为:

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

iframe 会有各自不同的 array 构造器...

接下来说 Array.isArray

console.log(Array.isArray(arr)); // true

Array.isArray 对在另一个领域里面创建的数组也会返回 true。它对Array 的子类也会返回 trueJSON.stringify 内部用的就是它。

但是,就像推上那位大佬说的,这不代表 arr 本身具有数组的方法。所有的方法们可能都被设成了 undefined,甚至 arrprototype 可能都没了。

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

下面是你遇见这种数组了怎么办..

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

Symbol 和 realm (领域)

看看这段代码:

<iframe srcdoc="<script>var arr = [1, 2, 3];</script>"></iframe>
<script>
  const iframe = document.querySelector('iframe');
  const arr = iframe.contentWindow.arr;

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

地球人都知道会 log 出来 1,2,3。但这说明了啥?
for-of 循环内部使用的是 arr[Symbol.iterator],这是跨领域之后还可以通用的。看:

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

每个领域有它自己的 Symbol 实例的同时,他们的 Symbol.iterator 却是一样的。

引用一句Symbol 同时是 JavaScript 里最特殊和最不特殊的东西了。

最特殊

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'

传给 Symbol 构造器的字符串只是对它的一个描述,同一个领域下面同一个描述的 symbol 也是独特的。

最不特殊

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'

Symbol.for(str) 新建了一个根据你字符串唯一的 symbol 对象。重点是,跨了领域之后,它还是一样的。。

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

所以这个也就基本上是 Symbol.iterator 为啥能行的原因了。

根据传统,自己手写个 is 函数

那么 symbol 对象可以让我们写出跨领域能用 is 方法:

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

class Whatever {
 static isWhatever(obj) {
   return obj && Boolean(obj[typeSymbol]);
 }
 constructor() {
   this[typeSymbol] = true;
 }
 // 或者下面这种不会被遍历出来
 get [typeSymbol]() {
   return true;
 }
}

const whatever = new Whatever();
Whatever.isWhatever(whatever); // true

这样唯一的一点不太好的地方,就是命名冲突了。别人也这么搞,而且还同名的话,就有问题了。

03-05 20:18