Масиви, символи та області

5 хв. читання

Масиви перевірки типів

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 може стати причиною хибних спрацювань.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 5.6K
Приєднався: 8 місяців тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід / Реєстрація