本文是中文翻譯硫眯,英文原文鏈接 https://jakearchibald.com/...
Allen Wirfs-Brock 在 Twitter上沒有查找到Array.isArray(obj)
的原理爷辙,并且找到了錯誤的答案普办。
數(shù)組類型檢查
function foo(obj) {
// …
}
假設obj
是一個數(shù)組,并且對其做些特殊處理驼鞭。舉例JSON.stringify
對數(shù)組做了特殊處理缨历。
我們可以這樣做:
if (obj.constructor == Array) // …
但是這樣不能判斷數(shù)組的派生:
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
傳播沾凄。
認真的看:
<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
有自己的數(shù)組構造函數(shù),它是和父頁面的是不同的知允。
輸入 Array.isArray
console.log(Array.isArray(arr)); // true
Array.isArray
判斷數(shù)組時會一直返回true
撒蟀,即使是在其他realm
創(chuàng)建的數(shù)組,它一直返回true
無論是派生的數(shù)組還是其他realm
中温鸽。這就是JSON.stringify
使用的保屯。
但是手负,正如 Allen 所揭示的,這并不意味這arr
有任何數(shù)組的方法姑尺。一些或者所有方法都可能被設置為undefined
竟终,甚至刪除掉數(shù)組的整個原型:
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‘切蟋,這是選擇最少的答案统捶。所以現(xiàn)在感覺挺時髦的。
總之敦姻,你想防御上述問題瘾境,你可以從數(shù)組原型上使用數(shù)組方法:
if (Array.isArray(noProtoArray)) {
const mappedArray = Array.prototype.map.call(noProtoArray, callback);
// …
}
Symbols and realms
看看這個:
<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>
上面會打印出1、2镰惦、3迷守。但是for-of
循環(huán)工作時會調(diào)用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
函數(shù)中的string
參數(shù)僅僅是個描述茵瘾,即使在相同的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)
會創(chuàng)建一個唯一的symbol和你傳遞的字符串綁定。有趣的是在不同的realm
中它得到的也是一樣的拗秘。
const iframe = document.querySelector('iframe');
const iframeWindow = iframe.contentWindow;
console.log(Symbol.for('foo') === iframeWindow.Symbol.for('foo')); // true
這就可以解釋Symbol.iterator
是怎么工作的圣絮。
創(chuàng)建自己的is
函數(shù)
如果我們想創(chuàng)建一個可以在不同realm
工作的is
函數(shù),可以通過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
名稱是在所有代碼中是唯一的凡涩。如果有別人創(chuàng)建了Symbol.for('whatever-type-symbol')
并賦予了其他含義棒搜,則isWhatever
可能會報錯。
(原文地址 http://www.reibang.com/p/a… 活箕,轉載需經(jīng)過作者同意A︳铩)