原文: 翻译:疯狂的技术宅
本文首发微信公众号:jingchengyideng
欢迎关注,每天都给你推送新鲜的前端技术文章Array 类型检测
function foo(obj) { // …}
假设obj
是一个数组,我们想要实现一些功能。比如 JSON.stringify
就是一个例子,它以不同的方式把数组输出到其他对象。
我们可以这样做:
if (obj.constructor == Array) // …
但是对于数组的子类来说这是错误的:
class SpecialArray extends Array {}const specialArray = new SpecialArray();console.log(specialArray.constructor === Array); // falseconsole.log(specialArray.constructor === SpecialArray); // true
所以如果你想检查子类的类型,那么应该用instanceof
:
console.log(specialArray instanceof Array); // trueconsole.log(specialArray instanceof SpecialArray); // true
但是当引入多个realm时,事情将会变得更加复杂:
Multiple realms
realm包含self
引用的JavaScript全局对象。 因此,可以说在worker中运行的代码与在页面中运行的代码处于不同的realm。 在iframe之间也是如此,但同源iframe也共享一个ECMAScript'代理',这意味着对象可以穿越 realm。
接着看代码:
这两个都是false,因为:
console.log(Array === iframe.contentWindow.Array); // false
iframe有自己的数组构造函数,它与父页面中的构造函数不同。
Array.isArray
console.log(Array.isArray(arr)); // true
Array.isArray
将为数组返回true,即使它们是在另一个realm中创建的。 对于任何realm的Array
的子类,它也会返回true。 这就是JSON.stringify
内部的处理方法。
但是,这并不意味着arr
有 array 方法。 有些甚至所有方法都已设置为undefined,或者数组可能已将其整个原型删除:
const noProtoArray = [];Object.setPrototypeOf(noProtoArray, null);console.log(noProtoArray.map); // undefinedconsole.log(noProtoArray instanceof Array); // falseconsole.log(Array.isArray(noProtoArray)); // true
不管怎样,如果要杜绝上述问题,可以通过Array原型调用Array的方法:
if (Array.isArray(noProtoArray)) { const mappedArray = Array.prototype.map.call(noProtoArray, callback); // …}
Symbols 与 realms
再看看这个:
上面的logs 1, 2, 3 很不引人注目,但 for-of 循环通过调用arr[Symbol.iterator]
来工作,这在某种程度上可以跨越realm。 这是如何做:
const iframe = document.querySelector('iframe');const iframeWindow = iframe.contentWindow;console.log(Symbol === iframeWindow.Symbol); // falseconsole.log(Symbol.iterator === iframeWindow.Symbol.iterator); // true
虽然每个realm都有自己的Symbol实例,但Symbol.iterator在各个realm都是相同的。
Symbols同时也是JavaScript中最独特和最独特的东西。
The most unique 多唯一性
const symbolOne = Symbol('foo');const symbolTwo = Symbol('foo');console.log(symbolOne === symbolTwo); // falseconst obj = {};obj[symbolOne] = 'hello';console.log(obj[symbolTwo]); // undefinedconsole.log(obj[symbolOne]); // 'hello'
传递给Symbol
函数的字符串只是一个描述。 即使在同一realm内,这些Symbol也是独一无二的。
The least unique 最小唯一性
const symbolOne = Symbol.for('foo');const symbolTwo = Symbol.for('foo');console.log(symbolOne === symbolTwo); // trueconst obj = {};obj[symbolOne] = 'hello';console.log(obj[symbolTwo]); // 'hello'
Symbol.for(str)
创建一个与传递它的字符串唯一的symbol。 有趣的是它在各个realms都是一样的:
const iframe = document.querySelector('iframe');const iframeWindow = iframe.contentWindow;console.log(Symbol.for('foo') === iframeWindow.Symbol.for('foo')); // true
这就是Symbol.iterator
大致的工作原理。
创建自己的 'is' 函数
如果我们想要创建我们自己的“is”函数并跨越realm会怎么样? 好吧,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
肯定返回false。
本文首发微信公众号:jingchengyideng
欢迎关注,每天都给你推送新鲜的前端技术文章