【译】Arrays, symbols, and realms

本文是中文翻译,英文原文链接 https://jakearchibald.com/...

Allen Wirfs-Brock 在 Twitter上没有查找到Array.isArray(obj)的原理,并且找到了错误的答案。

数组类型检查

function foo(obj) {
  // …
}

假设obj是一个数组,并且对其做些特殊处理。举例JSON.stringify对数组做了特殊处理。

我们可以这样做:

if (obj.constructor == 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

但是当引入多realms时事情就变得复杂了。

Multiple realms

realm 包含JavaScript全局对象,self引用的它,因此可以说程序在不同的页面中运行在不同的realm 里。在iframe中也是这样的,在同源iframe中共享 ECMAScript agent,这意味着对象可以在跨realm传播。

认真的看:



这两个都是false,因为:

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

...iframe有自己的数组构造函数,它是和父页面的是不同的。

输入 Array.isArray

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

Array.isArray判断数组时会一直返回true,即使是在其他realm创建的数组,它一直返回true无论是派生的数组还是其他realm中。这就是JSON.stringify使用的。

但是,正如 Allen 所揭示的,这并不意味这arr有任何数组的方法。一些或者所有方法都可能被设置为undefined,甚至删除掉数组的整个原型:

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

这就是我在 Allen 的投票中弄错的地方,我选择了’it has Array methods‘,这是选择最少的答案。所以现在感觉挺时髦的。

总之,你想防御上述问题,你可以从数组原型上使用数组方法:

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

Symbols and realms

看看这个:



上面会打印出1、2、3。但是for-of循环工作时会调用arr[Symbol.iterator],这是它可以跨realm工作的原因:

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

虽然每个realm都有自己的Symbol对象,但是Symblo.iterator在不同的realm都是相同的。

借用 Keith Cirkel 的话:SymbolsJavaScript中最独特和最不独特的东西。

最独特的

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函数中的string参数仅仅是个描述,即使在相同的realm中这些symbols都是唯一的。

最不独特的

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和你传递的字符串绑定。有趣的是在不同的realm中它得到的也是一样的。

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

这就可以解释Symbol.iterator是怎么工作的。

创建自己的is函数

如果我们想创建一个可以在不同realm工作的is函数,可以通过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

来自其他realm的实例、该实例的子类和删除原型的实例都可以使用这个方法。

唯一的小问题是你要手动保证Symbol名称是在所有代码中是唯一的。如果有别人创建了Symbol.for('whatever-type-symbol')并赋予了其他含义,则isWhatever可能会报错。

(原文地址 https://www.jianshu.com/p/a… ,转载需经过作者同意!)

进一步阅读

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

你可能感兴趣的:(【译】Arrays, symbols, and realms)