Масиви перевірки типів
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
Але все стає складнішим, якщо ви вводите декілька областей:
Декілька областей
Область містить глобальний об'єкт JavaScript, на який посилається self
. Тому, можна сказати, що код, який виконується у воркері, знаходиться в іншій області від коду, що виконується на сторінці. Те ж саме вірно і для iframes, але у випадку iframes з однаковим джерелом даних, також використовується 'агент' ECMAScript, що означає, що об'єкти можуть ... подорожувати крізь області.
Серйозно, гляньте:
<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>
Вони обидва хибні, тому що:
console.log(Array === iframe.contentWindow.Array); // false
... iframe має свій власний конструктор масивів, який відрізняється від того, що знаходиться на сторінці предка.
Array.isArray
console.log(Array.isArray(arr)); // true
Array.isArray
поверне true для масивів, навіть якщо вони були створені в іншій області. Він також поверне true для підкласів Array
з будь-якої області. Це те, що JSON.stringify
використовує внутрішньо.
Але це не означає, що 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
В будь-якому випадку, якщо ви хочете захиститись від вищевказаного, можете застосувати методи масивів із прототипу масиву:
if (Array.isArray(noProtoArray)) {
const mappedArray = Array.prototype.map.call(noProtoArray, callback);
// …
}
Символи та області
Погляньте на це:
<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 працюють за допомогою виклику arr[Symbol.iterator]
, і це якимось чином працює в різних областях. Ось як:
const iframe = document.querySelector('iframe');
const iframeWindow = iframe.contentWindow;
console.log(Symbol === iframeWindow.Symbol); // false
console.log(Symbol.iterator === iframeWindow.Symbol.iterator); // true
Хоча у кожної області є свій екземпляр Symbol
, Symbol.iterator
однаковий в різних областях. Символи одночасно є й найбільш унікальною і найменш унікальною річчю в 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
– лише опис. Символи унікальні навіть в межах однієї області.
Найменш унікальне
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)
створює символ, який настільки ж унікальний, наскільки й рядок, який ви передаєте. Цікавою частиною є те, що він однаковий в різних областях:
const iframe = document.querySelector('iframe');
const iframeWindow = iframe.contentWindow;
console.log(Symbol.for('foo') === iframeWindow.Symbol.for('foo')); // true
І це приблизно те, як працює Symbol.iterator
.
Створення нашої власної функції 'is'
Що якщо ми хочемо створити свою власну функцію 'is', яка працювала б в різних областях? Що ж, символи нам в цьому допоможуть:
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
Це працює навіть якщо екземпляр з іншої області, навіть якщо він є підкласом і навіть якщо його прототип видалений.
Єдина невеличка проблема – вам треба схрестити пальці й сподіватися, що ім'я символу є унікальним для всього коду. Якщо хто-небудь інший створить свій власний Symbol.for('whatever-type-symbol')
й використає його для позначення чогось іншого, isWhatever
може стати причиною хибних спрацювань.
Ще немає коментарів