Минутка занимательного яваскрипта

Чудесные маленькие находки и большие раздражающие странности в славном JavaScript

Невидимый title у детей button

Что не так?

Невероятно тупой вопрос: Всегда ли отображается атрибут title у видимых html-элементов?

Ответ, ожидаемо: Нет, не всегда. Например, его не хочет отображать Firefox для вложенных в button элементов.

Пример

Код

<button><span title="bar">foo</span></button>

Элемент

Почему так?

А это просто-напросто баг, открытый ещё в 2002 году.

А что говорит спецификация?

The title attribute represents advisory information for the element, such as would be appropriate for a tooltip W3C
The title global attribute contains a text representing advisory information related to the element it belongs to. Such information can typically, but not necessarily, be presented to the user as a tooltip. MDN

То есть title — штука справочная и не особо-то и обязательная. Скорее всего поэтому баг и висит уже 14 лет.

Что же делать?

Всё не так плохо, можно нагло заменять title псевдоэлементом. Решение предложено Антоном Немцовым.

JSX бьёт по рукам

Что не так?

Допустим, надо сделать из строки 'A,B' разметку

<i>A</i>
<b>B</b>

Вы хотите сделать это самым очевидным способом — обработав строку

formatter = input => input.replace('A', <i>A</i>).replace('B', <b>B</b>)

Но, вместо лёгкого решения, вы получаете какую-то фигню вроде [object Object],[object Object].

Почему так?

Мистер JSX только делает вид что HTML в нём — это строки. Это ни капельки не строки, а очень даже DOM-объекты. И операции работы со строками, которые вы пытаетесь провести своими грязными ручонками, попросту ничего не дадут.

В лучшем случае вы получите знаменитый [object][Object] вместо желаемой разметки.

Что же делать?

Ну, как обычно, страдать. Есть неплохой вариант — разбивать строку на массив элементов и каждому из них давать свой шаблон и складывать в массив-результат.

formatter = input => {let result = [];input.split(',').forEach(e => {switch (e) {case 'A':result.push(<i>{e}</i>);break;case 'B':result.push(<b>{e}</b>);break;}});return result}

М-м-м-м, сколько кода…

А просто сделать замены в строке, засунув туда HTML — нельзя.

Чудесный instanceof

Что не так?

'foo' instanceof String // false

Строка не является экземпляром класса Строка. Чудесно, правда?

Почему так?

Строка 'foo' создаётся через литерал строки — кавычки (''). Это не объект, а примитив, а у примитива нет прототипа.

Это относится ко всем примитивам, созданным через литералы — числам, строкам и булям.

true instanceof Boolean // false
42 instanceof Number // false

Но те же числа, строки и були, созданные через конструктор оператором new — очень даже являются экземплярами своих классов, потому как конструктор создаёт не примитив, но объект.

// НИ В КОЕМ СЛУЧАЕ НЕ ПОВТОРЯЙТЕ ЭТОГО ДОМА ИЛИ НА РАБОТЕ
// ЭТО МОЖЕТ ПРИВЕСТИ К САМОВОЛОСОВЫРЫВАНИЮ ИЛИ ПОБИТИЮ КОЛЛЕГАМИ
let foo = new String('bar');
foo instanceof String // true

Что же делать?

Ну как обычно, страдать. Но если вам всё равно надо узнать тип переменной, проверяйте, не примитив ли это через typeof.

И только если это не примитив (typeof выдаст 'object'), то имеет смысл проверять его прототип через instanceof.

Отрицательные индексы

Что не так?

let arr = [];
arr[-1] = 'foo'; // В массив записывается элемент с отрицательным индексом
arr; // [] — после этого массив выглядит пустым
arr[-1]; // foo — но это не так

В массив можно записать элемент с отрицательным индексом. При этом его не видно при вызове массива. Но он есть. Как суслик.

… Видишь суслика?
— Нет.
— И я не вижу. А он есть!
© прапорщик Казаков, фильм «ДМБ»

Почему так?

Несмотря на то, что индекс — это число, записывать элементы массива можно только по натуральным индексам. Попытка записать элемент на какой-либо другой индекс (например, объект, дробное число или отрицательное число) вызовёт преобразование индекса к строке и запись не элемента массива, а свойства массива по ключу.

Поэтому запись проходит успешно, но записывается не элемент, а свойство.

Что же делать?

Молиться, поститься и слушать радио «Радонеж». Да особо ничего. Просто при странных действиях не нужно ожидать адекватных результатов.

Точки с запятой

Что не так?

Найдите ошибку в коде, не запуская его

let a = 5

(function() {
  alert(a)
})()

Почему так?

TypeError: 5 is not a function

Пропущенная точка с запятой приводит к тому, что самовызываемая функция превращается в аргумент при выполнении числа 5 как функции.

Это равноценно такой записи

let a = 5(function() {alert(a)})()

5 — примитив, его нельзя выполнить и из-за этого скрипт умирает.

ПОТРАЧЕНО

Корректная запись

let a = 5;

(function() {
  alert(a)
})();

Что же делать?

Соблюдайте стиль кодирования и всегда ставьте точки с запятой и фигурные скобки.

Конструктор дат

Что не так?

String(new Date(2015, 0)) === String(new Date('2015-01')) // false

Первое января, которое создаётся конструктором new Date из нескольких аргументов не равно первому января, созданному конструктором new Date из строки в ISO-формате.

Почему так?

А что же создаётся в том и другом случае?


String(new Date(2015, 0))   // "Thu Jan 01 2015 00:00:00 GMT+0300"
String(new Date('2015-01')) // "Thu Jan 01 2015 03:00:00 GMT+0300"

Ух ты. Смещение часового пояса. Друг мой, а почему ж ты в одном случае учитываешься, а во втором — нет?

Обратимся к документации, там наверняка есть логичное объяснение.

Обратите внимание: если функция Date вызывается в качестве конструктора с более чем одним аргументом, то указанные аргументы интерпретируются как локальное время. Если аргументы указывают время в UTC, используйте new Date(Date.UTC(...)) с теми же аргументами.

© MDN

Иными словами

ПРОСТ))00))

Что же делать?

Читать документацию, работать с датами только одним из этих способов, либо черёз обёртки.