Операторы сравнения
Сравнение – это вопрос к данным: «пять больше трёх?», «пароли совпадают?». Ответ всегда один из двух – true или false, те самые булевы значения из главы 2.
Операторы порядка пишутся как в математике: > – больше, < – меньше, >= – больше или равно, <= – меньше или равно. Каждый сравнивает два значения и возвращает true или false – ответ можно напечатать, сохранить в переменную и использовать везде, где программа ждёт булево значение:
console.log(5 > 3); // true// false – 2 не больше и не равно 3console.log(2 >= 3);console.log(4 < 10); // true// true – равенство «меньше или равно» засчитываетconsole.log(7 <= 7);const score = 82;// сохраняем булев результат сравнения в переменнуюconst passed = score >= 50;// true – с ним можно работать дальше, как с любым значениемconsole.log(passed);А вот равенств в языке два – нестрогое == и строгое === (с парными неравенствами != и !==). Чем они различаются и какое выбирать – разберём в этом уроке, а пока запишем равенство так же, как в математике:
// true – значения совпадаютconsole.log(7 == 7);// true – «не равно» тоже вопросconsole.log(7 != 8);const pin = 2409;// true – ввод совпал с сохранённым пин-кодомconsole.log(pin == 2409);Граница: >= и <=
Строгие > и < границу не включают, а >= и <= – включают. На числовой прямой разница видна сразу: точка на границе либо выколота, либо закрашена.
// false – 10 не больше 10console.log(10 > 10);// true – 10 равно 10, и >= это засчитываетconsole.log(10 >= 10);const age = 18;// true – 18 равно 18: условие «с 18 лет» выполненоconsole.log(age >= 18);// false – 18 не больше 18console.log(age > 18);У < и <= всё зеркально – луч решений уходит влево от границы:
const boxes = 5;// false – 5 не меньше 5console.log(boxes < 5);// true – 5 равно 5, и <= это засчитываетconsole.log(boxes <= 5);Один символ меняет смысл условия: «скидка от 1000 рублей» – это total >= 1000. Возьмёте > – и заказ ровно на тысячу останется без скидки.
Исторически первым в языке появилось нестрогое равенство ==. Если типы операндов разные, оно сначала приводит их к одному типу по собственным правилам – чаще всего преобразуется одна сторона – и только потом сравнивает. Исключение – пара null и undefined: они сравниваются без всякого приведения.
На практике это рождает результаты, которые не угадать: пустая строка оказывается равна нулю, false – строке '0'. Правила приведения у == свои собственные, они не совпадают с Number() дословно, и их приходится заучивать:
// true – пустая строка приведена к нулюconsole.log('' == 0);// true – строка '7' приведена к числу 7console.log('7' == 7);// true – оба приведены к нулюconsole.log(false == '0');// false – у null своё особое правилоconsole.log(null == 0);// true – эта пара равна только друг другуconsole.log(null == undefined);Чем это опасно в настоящей программе? Приведение типов при == не бросает ошибку: программа не падает и ничего не сообщает – просто продолжает работать с неверным результатом. Например, данные из поля ввода: пользователь ничего не ввёл, у вас пустая строка ''. Сравнение '' == 0 даёт true – программа не отличает «ничего не ввели» от «ввели ноль», хотя это два разных случая:
// поле пустое: пользователь ничего не ввёлconst field = '';// false – «ничего не ввели» и «ввели 0» различимыconsole.log(field === 0);// поле пустое: пользователь ничего не ввёлconst field = '';// true – == привёл пустую строку к нулюconsole.log(field == 0);Решение в языке уже есть: строгое равенство ===. Оно ничего не приводит: типы разные – сразу false, разговор окончен. Ответ зависит только от того, что вы сравниваете: '5' === 5 – false, 5 === 5 – true. Пара к нему – строгое неравенство !==.
// false – строка не число, случаи различимыconsole.log('' === 0);// false – тип на местеconsole.log('7' === 7);// true – тот же тип, то же значениеconsole.log(7 === 7);// true – типы разные, значит строгого равенства нетconsole.log(2 !== '2');Правил приведения у == много, они неочевидны, и держать их в голове ненадёжно. Договоримся: в этом курсе – только === и !==. Так же пишут стайлгайды больших компаний, а линтеры – инструменты автоматической проверки кода – помечают == как ошибку.
Знаки > и < работают не только с числами – но со строками принцип другой. Главное: внутри JavaScript у каждого символа – буквы, цифры, знака – есть свой числовой код. У «а» один код, у «б» – следующий, у заглавных – свои собственные. Строки сравниваются посимвольно слева направо по этим кодам:
| Символ | Код |
|---|---|
'1' | 49 |
'2' | 50 |
'Б' | 1041 |
'а' | 1072 |
'б' | 1073 |
'н' | 1085 |
'т' | 1090 |
Сравнение идёт по парам символов, слева направо: первый символ левой строки против первого символа правой, второй против второго. Пока коды в паре совпадают, разбор переходит к следующей паре. Как только коды в паре разошлись, ответ определён: меньше та строка, у которой код в этой паре меньше. Остальные символы не читаются.
Проследите по шагам на паре 'банан' и 'бант' – подсвечена текущая пара символов:
Пограничные случаи
Пары могут кончиться раньше, чем нашлось различие – так бывает, когда одна строка целиком совпала с началом другой. Вот тут – и только тут – в дело вступает длина: короткая строка считается меньшей.
// true – «кот» кончился, различий не нашлосьconsole.log('кот' < 'коты');// false – та же пара, обратный вопросconsole.log('коты' < 'кот');// true – пустая строка меньше любой непустойconsole.log('' < 'а');Если строки совпали целиком, ни одна не меньше и не больше – строгие < и > отвечают false. Здесь и пригождаются <= и >=: равенство они засчитывают. А на вопрос «это одна и та же строка?» отвечает ===.
// false – ни одна не меньшеconsole.log('кот' < 'кот');// true – равенство засчитаноconsole.log('кот' <= 'кот');// true – строки совпадают целикомconsole.log('кот' === 'кот');Регистр меняет ответ: коды заглавных букв меньше кодов строчных – Б это 1041, а б – 1073. Поэтому слово с заглавной оказывается меньше слова со строчной, даже когда по алфавиту всё наоборот. Лечится приведением к одному регистру – методом toLowerCase() из урока «Строки».
// true – 1041 меньше 1072console.log('Банан' < 'апельсин');// true – та же причина, а не алфавитconsole.log('Яблоко' < 'абрикос');// false – в одном регистре порядок привычныйconsole.log('банан' < 'апельсин');// false – регистр убрали, коды в одном регистреconsole.log('Банан'.toLowerCase() < 'апельсин');У пробела код 32 – меньше любой буквы и любой цифры. Поэтому лишний пробел в начале меняет ответ, а данные из полей ввода перед сравнением обычно чистят методом trim(): он срезает пробелы по краям строки (подробно – в уроке «Методы строк»).
// true – пробел (32) меньше буквы «к»console.log(' кот' < 'кот');// false – после чистки строки одинаковыconsole.log(' кот'.trim() < 'кот');Из того же правила следует результат, который легко принять за ошибку: '2' > '12' – true. Это не числа, а строки: сравнились коды первых символов, код «2» больше кода «1», и на этом разбор закончился. Нужно числовое сравнение – превратите строки в числа, унарный плюс из урока «Преобразования типов» как раз для этого.
// true – это строки: сравнились коды «2» и «1»console.log('2' > '12');// false – унарные плюсы сделали числа, сравниваются числаconsole.log(+'2' > +'12');Порядок как в словаре
Сравнение по парам символов – не выдумка языка: так устроен словарь и список контактов в телефоне. Отыскивая «Морозова», вы не считаете буквы в фамилиях, а открываете список на «М», потом смотрите вторую букву, третью – и останавливаетесь на первом расхождении.
Отсюда и порядок в списке: «Абрамов» стоит раньше «Мороза», хотя он длиннее – различие нашлось уже на первой букве. А «Мороз» раньше «Морозова» по другой причине: расхождений не нашлось, и первая фамилия просто кончилась. На этих двух правилах и держится сортировка списка по алфавиту.
// true – «А» раньше «М», длина не участвуетconsole.log('Абрамов' < 'Мороз');// true – расхождений нет, «Мороз» кончился первымconsole.log('Мороз' < 'Морозов');Но «как в словаре» получается не всегда – даже внутри одного языка и одного регистра. Русская ё по коду стоит после я (1105 против 1103), поэтому «ёлка» окажется больше «яблока», хотя в словаре всё наоборот. Разные алфавиты тоже идут блоками: вся латиница расположена раньше кириллицы.
Когда порядок нужен человеческий – для списка на экране, – строки сравнивают методом localeCompare: он учитывает правила языка и отвечает не true/false, а числом: отрицательное – левая строка раньше, ноль – для порядка они одинаковы, положительное – раньше правая. Гарантируется только знак – не проверяйте результат на === -1. Локаль стоит передавать явно ('ru'), иначе поведение зависит от окружения.
// true – код «ё» (1105) больше кода «я» (1103)console.log('ёлка' > 'яблоко');const result = 'ёлка'.localeCompare('яблоко', 'ru');// true – по правилам языка «ёлка» раньшеconsole.log(result < 0);// теперь вы знаете
- Сравнения возвращают boolean:
>,<,>=,<=,===,!== >=и<=засчитывают равенство, строгие>и<– нет: от этого зависит граница условия===сравнивает без преобразования типов – используйте всегда==преобразует типы по собственным правилам – их результат трудно предсказать- Строки сравниваются по парам символов слева направо: ответ определяет первая различающаяся пара
- Длина влияет только в одном случае: общие символы совпали и одна строка кончилась – она меньше
- Коды заглавных меньше строчных, а «ё» стоит после «я» – это порядок кодов, а не алфавит
Скидка действует «от 1000 рублей». Какая проверка ей соответствует?
Что выведут две строки?
console.log('лес' < 'лес');console.log('лес' <= 'лес');
Что выведут две строки?
console.log('100' > '99');console.log(+'100' > +'99');
Что выведет console.log('дом' < 'домик')? Впишите ответ словом.