Шпаргалка по операторам сравнения и преобразованию типов в JS

Тема сравнения значений и тесно переплетенная с ней тема приведения типов относятся к самой фундаментальной области знаний для программистов на Javascript, без этих знаний будет легко допустить ошибки, и в каких-то моментах поведение программы может показаться нелогичным. Эта тема — такой же фундамент, как и тема функций и ООП в JS, о которых я уже писал цикл статей (кстати, надо бы сесть и обновить их, привести к текущим реалиям языка). Итак, постараюсь изложить максимально коротко, насколько это позволяют важность темы и подводные камни языка.

Как известно, для управления ходом программы (в операторах if, for, while…) или для выбора значения (тернарный оператор … ? … : … ) используются логические значения — true и false. Получить их можно двумя способами (мы не будем рассматривать логические операторы, такие как &&, ||, они вторичны).

Первый способ: получить логическое значение путем преобразования типов

Например:

Правила преобразования довольно простые:

false получается из:

  • Числа 0, специального значения NaN (Not a Number — число, которое не число, а ошибка)
  • null
  • undefined
  • Пустой строки (совсем пустой, 0 символов)

true получается из всего остального:

  • Любого числа, кроме 0 и NaN
  • Непустой строки, в том числе, состоящей из пробелов или «0»
  • Любого объекта, в том числе «пустого», пустого массива, функции, и даже если нечаянно создать объект класса Boolean:

Кстати, как узнать, что в переменной объект? Нужно определить, к какому из 6 типов она относится — это три примитивных (number, string, boolean), два специальных пустых типа (undefined, null) и, собственно, объектный тип (object). Для этого в JS существует оператор typeof, но у него есть некоторые тонкие моменты:

Определить, что в переменной массив, можно двумя способами:

Второй способ: операторы сравнения

Строгое равенство (===)

Самый простой и понятный вариант: два операнда сравниваются сначала по типам, если тип один, идет сравнение по значению. Для примитивных типов есть одно исключение — NaN при любых сравнениях, даже с собой, всегда дает false. А объекты равны только в случае, если оба операнда ссылаются на один и тот же объект:

Нестрогое или абстрактное равенство (==)

Запомните, что это опасная штука со множеством нюансов:

  • null == undefined дает true — это надо запомнить (null === undefined при этом дает false)
  • Нестрогое проверка на равенство null и undefined с чем угодно другим всегда дает false
  • NaN, как и при строгом равенстве, ни с чем сравнивать нельзя — всегда будет false
  • Два объекта равны, только если это один и тот же объект
  • Два примитива одного типа сравниваются по значению — тут без сюрпризов
  • Два операнда разного типа приводятся к числовому типу и после этого сравниваются по значению (об этом будет ниже)

Сравнение на больше-меньше (>, <, >=, <=)

  • Числа сравниваются без сюрпризов, не забываем про NaN, сравнение с которым всегда дает false
  • Две строки сравниваются лексикографически, то есть «как в словаре», но учитывая расположение символов в кодовой таблице Unicode
  • Во всех остальных случаях оба операнда приводятся к числовому типу и получившиеся значения сравниваются

Приведение к числовому типу в Javascript

  • null и false превращаются в 0
  • true превращается в 1
  • undefined превращается в NaN со всеми вытекающими проблемами для сравнения — любое сравнение даст false!
  • У строк сначала отбрасываются пробельные символы в начале и в конце, если ничего не осталось, то это 0, если остается какое-то число, в том числе с десятичной точкой («1.23»), с минусом в начале или заданное через экспоненту («1e3» — это 1*10³, то есть 1000), то оно считывается, иначе — это NaN, опять же, не сравнимая ни с чем величина. Учтите, что функции parseInt() и parseFloat() работают немного по-другому — они позволяют иметь в строке после числа любые другие символы, они просто проигнорируются. Кроме того, parseInt() умеет получать из строки не только десятичные числа, но и числа с другими основаниями — двоичные, шестнадцатеричные и т.д., но в случае строки «1e3» считывает только 1, остальное игнорирует
  • У объектов вызывается метод valueOf(), а если его нет или он возвращает не примитив, то вызывается toString(). Далее, если полученный примитив не является числом, то идет следующий этап приведения к числу по правилам выше.

Некоторые дефолтные преобразования объектов в числа (если мы не задали соответствующие методы для объекта или его прототипа):

  • Метод toString() у массивов дает строку, в которой через запятую перечислены элементы массива. Эту строку пробуем затем превратить в число, и тут уже все зависит от содержимого массива, см. примеры ниже
  • Тот же метод для обычных объектов возвращает строку «[object Object]», поэтому результатом преобразования в число будет NaN
  • Для функций возвращается строка с содержимым кода функции, начиная со слова function, поэтому преобразование в число снова дает NaN
  • Для дат (объектов класса Date) метод valueOf() вернет количество миллисекунд с начала Эпохи Юникса. Поэтому есть интересное следствие: если есть два объекта даты, указывающие на одну и ту же временную точку, то строгое или нестрогое равенство (===, ==) вернет false, потому что это разные объекты, а вот если сравнить через >= или <=, либо же оба операнда принудительно привести к числу, то вернется true, потому что число миллисекунд одинаково.

Примеры преобразований к числу (этого можно добиться, воспользовавшись унарным плюсом или через Number()):

Разные примеры сравнения, в том числе затейливые:

Последний пример кажется совсем бредом, но с точки зрения JS, все логично: сначала массив справа приводится к булевому типу, а любой объект — это true, с помощью оператора отрицания (!) значение превращается в false (собственно, из-за этого оператора массив и преобразуется в логическое значение). Далее false сравнивается нестрого с массивом — задействуется механизм приведения к числовому типу, и оба операнда превращаются в 0, получается 0 === 0.

А вот еще пример, когда оказывается, что истина истине рознь, и проявляются нюансы автоматических преобразований типов, заложенные в языке:

Важное замечание. Я не рассматривал операторы != и !==, потому они — по сути, оператор отрицания, примененный к результату операторов равенства == и === соответственно. То есть x != y — это то же самое, что !(x == y).

Бонус: оператор switch-case

Не забывайте, что оператор switch-case производит проверку на строгое равенство (===), поэтому могут возникнуть казусы, например, если вы берете значение для проверки из формы — оно вам вернется строкового типа, его нельзя сравнивать с числами в case:

В данном примере ничего не произойдет, потому что в переменной answer значение типа string, а в разделах case — значения типа number.

Мораль и итог

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

Добавить комментарий