本文是中文翻译,英文原文链接 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 的话:Symbols
是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
函数中的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