Добро пожаловать в мир безумия JavaScript

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

В этой статье я хочу познакомить вас с некоторыми из этих причуд.

1. Равенство и одинаковость

Есть тонкие, но важные различия в том, как JavaScript определяет равенство и сходство. Для новичков, вероятно, наиболее запутанной является разница между абстрактной == и строгой === проверкой равенства.

Для начала рассмотрим несколько примеров:

Как насчет сравнения с null?

Если преобразовать null в логическое значение, получится false.
Однако, если вы попытаетесь сравнить null с false, результат все равно будет false!

Что, если мы сравним другие ложные значения, такие как 0 или "", с false?
Тогда вы можете предположить, что это тоже будет false. Это справедливо только для строгой проверки на равенство, абстрактное выражение приведет к true!

В приведенном выше примере мы видели, что проверка абстрактного равенства в некоторых случаях классифицирует две переменные разных типов как равные. Однако, если вы сравните пустой массив, который является истинным значением, с true, вы снова можете удивиться:

Важно знать разницу между сравнением абстрактного и строгого равенства в JavaScript. Я настоятельно рекомендую прочитать эту статью от MDN, где они подробно объясняют равенство и сходство.



2. Математическое безумие

Продолжаем путь к математике! В JavaScript есть несколько замечательных особенностей:

Почему первый пример верен, а второй нет? Причина в том, что первый работает только в этом конкретном случае. Если вы посмотрите на порядок, в котором JavaScript оценивает эти выражения, становится ясно, почему вы получаете эти результаты:

Еще одна ловушка, когда дело доходит до математики в JavaScript, - это «магически» возрастающие числа:

Из-за стандарта IEEE 754 –2008 JavaScript округляет в этом масштабе до следующего ближайшего четного числа. То же самое и для многих других языков программирования, не только для JavaScript. Стандарт также отвечает за следующую проблему:

Проблема подробно объясняется в этом ответе на StackOverflow.

Также важно понимать, как JavaScript выбирает между сложением и объединением. В общем, можно сказать, что каждый раз, когда дополнительно задействуется строка, в какой-то момент операции она становится конкатенацией.

Однако есть несколько удивительных случаев, когда добавление становится совершенно другим типом, как вы можете видеть в примере ниже:

Подождите, я оставил самый запутанный пример на конец. Давайте сравним результаты функций Math.min() и Math.max()

Это определенно не то, что я ожидал, когда впервые использовал эту функцию. Чарли Харви объяснил такое поведение в своем блоге, так что загляните туда, если вас интересуют подробности.

3. Развлечение с массивами

Мы уже видели несколько примеров с массивами в предыдущих разделах, но есть еще много интересных фактов о массивах.

Например, если вы попытаетесь сложить два массива вместе, вы получите следующее:

Поначалу это может сбивать с толку, но становится понятным, если вы понимаете порядок, в котором выполняется эта конкатенация.

Также интересно, как JavaScript работает с конечными запятыми.
В массивах конечные запятые будут игнорироваться, как вы можете видеть в этом примере:

Но что, если у вас несколько конечных запятых? Вот как MDN описывает поведение JavaScript для этого примера:

Если используется более одной запятой, создается пробел (или дыра). Массив с отверстиями называется разреженным (плотный массив не имеет отверстий). При итерации массивов, например, с Array.prototype.forEach() или Array.prototype.map(), дыры в массиве пропускаются.

Вы можете видеть, что первые две запятые образуют ранее описанные дыры. Однако последняя запятая будет проигнорирована, как и в случае с обычными элементами массива. Вот как мы получаем окончательную длину 5.

4. Сложные стрелочные функции

Представленные в ES6 стрелочные функции быстро стали популярными из-за их минимального синтаксиса и более интуитивного поведения объекта this.

Распространенная ошибка, с которой уже сталкивались почти все, кто работал со стрелочными функциями, - это когда вы возвращаете значение в своей функции:

Здесь важно не пропустить фигурные скобки; в противном случае ваша функция вернет undefined, потому что вы открыли функцию, не вернув никакого значения.

Чтобы узнать больше о стрелочных функциях в JavaScript, вы можете взглянуть на документацию MDN.



5. Струны - это не струны?

Да, вы правильно прочитали. Взгляните на следующий пример:

Вы можете видеть, что typeof работает, как ожидалось, но instanceof возвращает false для String, хотя мы знаем, что "str" действительно является строкой.

Однако, если вы воспользуетесь конструктором String(), вы получите ожидаемый результат:

Что является причиной этого? MDN описывает instanceof так:

Оператор instanceof проверяет, появляется ли свойство prototype конструктора где-нибудь в цепочке прототипов объекта. Возвращаемое значение - логическое значение.

Причина этого в том, что строковый примитив не совсем то же самое, что объект String. Если вы все еще хотите ввести проверку примитива, вам следует использовать typeof вместо instanceof.

6. Бонус: HTML в JavaScript!

Знаете ли вы, что вы можете написать немного HTML на JavaScript? Как вы можете видеть в следующем примере, HTML-комментарии в JavaScript вполне допустимы.

Это было введено очень давно, поэтому старые браузеры, которые еще не понимали тег <script>, не давали сбой. Эти браузеры давно умерли, но функция осталась. Даже в NodeJS!

Это был лишь крошечный набор из многих особенностей JavaScript. Если вы хотите погрузиться глубже, я могу порекомендовать этот замечательный репозиторий GitHub от denysdovhan под названием wtfjs.