聊聊 Array 中的坑
原文:https://jakearchibald.com/201...
翻譯:瘋狂的技術宅
本文首發微信公眾號: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); // false console.log(specialArray.constructor === SpecialArray); // true
所以如果你想檢查子類的型別,那麼應該用instanceof
:
console.log(specialArray instanceof Array); // true console.log(specialArray instanceof SpecialArray); // true
但是當引入多個realm時,事情將會變得更加複雜:
Multiple realms
realm包含self
引用的JavaScript全域性物件。 因此,可以說在worker中執行的程式碼與在頁面中執行的程式碼處於不同的realm。 在iframe之間也是如此,但同源iframe也共享一個ECMAScript'代理',這意味著物件可以穿越 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有自己的陣列建構函式,它與父頁面中的建構函式不同。
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); // undefined console.log(noProtoArray instanceof Array); // false console.log(Array.isArray(noProtoArray)); // true
不管怎樣,如果要杜絕上述問題,可以通過Array原型呼叫Array的方法:
if (Array.isArray(noProtoArray)) { const mappedArray = Array.prototype.map.call(noProtoArray, callback); // … }
Symbols 與 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>
上面的logs 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例項,但Symbol.iterator在各個realm都是相同的。
Symbols同時也是JavaScript中最獨特和最獨特的東西。
The most unique 多唯一性
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
函式的字串只是一個描述。 即使在同一realm內,這些Symbol也是獨一無二的。
The least unique 最小唯一性
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。 有趣的是它在各個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
歡迎關注,每天都給你推送新鮮的前端技術文章