Операторы TypeScript на практике: математические, сравнительные, логические и тернарные
Практический обзор операторов арифметических вычислений, присваивания, сравнения, логических операций, увеличения и тернарных операторов в TypeScript, включая проверку равенства, которую компилятор отклоняет.
Как только вы поймете, как TypeScript присваивает и проверяет типы, следующим шагом будет работа с значениями: их сложение, сравнение, объединение условий и выбор между альтернативами. Операторы — это символы, которые выполняют эту работу, и большинство из них ведут себя точно так же, как и в JavaScript. Однако некоторые из них скрывают детали, которые часто приводят к ошибкам, такие как различия между слабым и строгим сравнением равенства, а также разница между x++ и ++x.
В этом руководстве рассматриваются арифметические, присваиваемые, относительные, логические, операторы увеличения и уменьшения, а также тернарные операторы с простыми примерами, которые можно проанализировать без их выполнения. По ходу вы увидите, где компилятор TypeScript добавляет дополнительные меры безопасности, отсутствующие в обычном JavaScript.
Сначала краткий обзор: несколько объявлений в одном операторе
Прежде чем рассматривать операторы, стоит упомянуть один синтаксический удобство, от которого зависят последующие примеры. Обычным способом объявления двух чисел является использование по одному оператору на переменную:
let a: number = 10;
let b: number = 20;
Один оператор let также может использоваться для объявления нескольких переменных сразу, если их разделить запятыми:
let a: number = 10, b: number = 20;
Обе формы компилируются в одинаковый результат; вторая просто более краткая. Каждая переменная в списке имеет свою собственную аннотацию, поэтому они могут иметь разные типы:
let a: number = 10, name: string = "John", isActive: boolean = true;
Используйте эту форму осторожно. Она хорошо читается для пары тесно связанных значений, таких как a и b, используемых в этом руководстве, но строка, сочетающая счётчик, имя и флаг, сложнее для восприятия, чем три отдельных объявления.
Арифметические операторы
Это знаки, знакомые нам по школьной математике. Примеры ниже начинаются с двух чисел:
// Arithmetic operators
let a: number = 10, b: number = 20;
Каждая строка применяет один оператор и показывает результат в комментарии:
console.log(a + b); // 30 → addition
console.log(a - b); // -10 → subtraction
console.log(a * b); // 200 → multiplication
console.log(a / b); // 0.5 → division
console.log(a % b); // 10 → modulus (remainder after division)
console.log(a ** b); // 100000000000000000000 → exponentiation (10 to the power 20)
Что делает каждый оператор:
+складывает два операнда.-вычитает правый операнд из левого.*умножает их./делит их без округления.10 / 20действительно равно0.5; отдельное целочисленное деление, как в некоторых других языках, отсутствует, поскольку каждое число в JavaScript является числом с плавающей точкой.%возвращает остаток.10 % 20равно10: двадцать входит в десять нулевой раз, поэтому весь десяток остается остатком.**поднимает левый операнд в степень правого.10 ** 20— это десять, умноженное на само себя двадцать раз, что объясняет крайне длинный результат.
Одно предупреждение относительно использования ** с большими значениями: числа, превышающие Number.MAX_SAFE_INTEGER (примерно девять квадриллионов), теряют точность целых чисел. Выражение 10 ** 20 выводится точно, но арифметические операции с такими значениями не являются надежными; используйте тип bigint, когда вам нужны точные большие целые числа.
Присваивание и сложное присваивание
Обычный оператор = сохраняет значение в переменной. JavaScript и TypeScript также предоставляют операторы сложного присваивания, которые выполняют арифметическую операцию и сохраняют результат в одном выражении. Начнем с двух значений:
let a: number = 100, b: number = 50;
Затем обновим a дважды: сначала традиционным способом, а затем с использованием сокращения:
console.log(a); // 100
a = a + b;
console.log(a); // 150
console.log(a += b); // 200
Пошагово: a начинает с значения 100. Выражение a = a + b считывает текущее значение a, прибавляет к нему b (50) и записывает результат 150 обратно в a. Здесь нет ничего нового, кроме оператора +, рассмотренного в предыдущем разделе.
В последней строке используется a += b, что означает точно то же самое, что и a = a + b, только более компактно. Поскольку в этот момент a равно 150, после выполнения операции оно становится 200. Ассигнование также является выражением, значение которого — только что сохранённое число; поэтому передача a += b напрямую в console.log выводит 200. Это работает, но скрытие изменения состояния внутри аргумента функции затрудняет понимание кода, поэтому в реальном коде лучше выполнять обновление на отдельной строке.
Такая же схема применяется ко всем арифметическим операторам: -=, *=, /=, %= и **= — они вычитают, умножают, делят, находят остаток или вычисляют степень, а затем присваивают результат.
Операторы отношений
Операторы сравнения анализируют два значения и всегда возвращают логическое значение true или false. Вот операнды:
let a: number = 10;
let b: number = 20;
А вот сравнения:
console.log(a > b); // false
console.log(a < b); // true
console.log(a === b); // false
console.log(a >= b); // false
console.log(a <= b); // true
console.log(a == b); // false
console.log(a != b); // true
Операторы сравнения понятны сами по себе: > (больше), < (меньше), >= (больше или равно) и <= (меньше или равно). Операторы равенства ==, === и != проверяют, совпадают ли два значения, но == и === работают по разным правилам, и эта разница заслуживает отдельного рассмотрения. Строгим аналогом != является !==, и именно его следует использовать в большинстве случаев.
Расслабленное равенство против строгого равенства
Эти два оператора равенства отличаются тем, как они обрабатывают типы:
==представляет собой расслабленное равенство. Если у операндов разные типы, он незаметно преобразует один из них перед сравнением значений.
=== означает строгое равенство. Он требует одинакового типа и одинакового значения, не выполняет преобразований и возвращает false, когда типы различаются.Чтобы показать это, возьмем число и строку, похожие друг на друга. Оба намеренно помечены как any; причина этого станет ясна вскоре.
let num1: any = 10;
let num2: any = "10";
Сравнение их с помощью каждого оператора дает разные результаты:
console.log(num1 == num2); // true → same value, type is ignored
console.log(num1 === num2); // false → same value, but number ≠ string
num1 содержит число 10, а num2 — строку "10". Расслабленное сравнение преобразует строку в число, находит 10 с обеих сторон и возвращает true. Строгое сравнение полностью игнорирует преобразование; число никогда не может быть равно строке, поэтому результат — false. Поскольку правила преобразования при расслабленном сравнении дают неожиданные результаты (например, 0 == "" равно true), большинство руководств по стилю и инструментов проверки кода рекомендуют использовать === и !== во всех случаях.
Почему в примере нужен any
Аннотации any не являются случайными. Если вы объявите переменные с их реальными типами, let num1: number = 10; и let num2: string = "10";, а затем напишете num1 === num2, код не скомпилируется. Компилятор сообщит об ошибке примерно в таком виде:
This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.
TypeScript понимает, что значение типа number и значение типа string никогда не могут совпадать, поэтому считает такое сравнение ошибкой. Эта проверка применяется как к оператору ==, так и к оператору ===. Если обе переменные объявлены как any, анализ таких несовпадений отключается, и это единственная причина, по которой демонстрация компилируется. Это небольшой, но полезный пример того, как система типов предотвращает бессмысленные сравнения до запуска программы, чего обычный JavaScript никогда не сообщает. Это также показывает, почему использование any имеет негативные последствия: оно бесшумно отключает именно такую защиту. Для альтернатив см. шесть типобезопасных замен any.
Логические операторы
Логические операторы объединяют условия. Их существует три: && (И), || (ИЛИ) и ! (НЕ). Начните с двух логических значений:
let b1 = true;
let b2 = false;
Затем объедините их и примените оператор НЕ:
console.log(b1 && b2); // false
console.log(b1 || b2); // true
console.log(!b2); // true
console.log(!b1); // false
Полное поведение операторов И и ИЛИ для каждой пары входных значений:
trueиtrue:&&возвращаетtrue,||также возвращаетtrue.trueиfalse:&&возвращаетfalse,||возвращаетtrue.falseиtrue:&&возвращаетfalse,||возвращаетtrue.falseиfalse:&&возвращаетfalse,||также возвращаетfalse.
А для оператора NOT:
!trueравноfalse.!falseравноtrue.
По-простому:
&&(И) — строгий оператор. Он возвращаетtrue, только если оба значения равныtrue; наличие хотя бы одногоfalseприводит к результатуfalse.||(ИЛИ) — более гибкий оператор. Достаточно наличия хотя бы одногоtrue; результат будетfalseтолько тогда, когда оба значения равныfalse.!(NOT) принимает один операнд и инвертирует его значение:trueпревращается вfalse, и наоборот.
Важный момент: ! всегда возвращает логическое значение, тогда как && и || на самом деле возвращают один из своих операндов. При использовании логических значений результат всегда остается логическим, как и ранее. При других значениях выражение "" || "default" дает результат "default", а выражение user && user.name — либо user, либо user.name. Оба оператора также работают по принципу сокращенной обработки: если результат уже определён на левой стороне, правая сторона никогда не обрабатывается. Именно поэтому их часто используют для задания значений по умолчанию и проверки условий, причём TypeScript соответственно задаёт тип результатов.
Сочетание сравнений с логическими операциями
Относительные и логические операторы обычно используются вместе: сравнения генерируют логические значения, а логические операторы соединяют их.
console.log(20 > 10 && 20 < 10); // false
console.log(20 > 10 || 20 < 10); // true
В первой строке 20 > 10 равно true, а 20 < 10 — false. Оператор И требует, чтобы оба выражения были true, поэтому результат — false.
Во второй строке оба сравнения одинаковы, но оператор ИЛИ требует лишь наличия хотя бы одного true, поэтому результат — true. Поскольку операторы сравнения имеют более высокий приоритет, чем && и ||, здесь не нужны скобки, хотя их использование может улучшить читаемость более сложных условий.
Увеличение и уменьшение
++ добавляет 1 к числу, а -- вычитает 1. Каждый из этих операторов существует в двух формах — префиксной и постфиксной, и различие между ними часто вызывает путаницу. Начнем с одной переменной:
let x: number = 10;
Когда оператор используется как отдельное выражение, обе формы дают одинаковый результат:
x++; // same as x = x + 1
console.log(x); // 11
++x; // same as x = x + 1
console.log(x); // 12
Если записать x++ и ++x на отдельных строках, оба варианта просто увеличивают значение x на один. Разница в их поведении проявляется только тогда, когда значение выражения используется в том же операторе, например при присваивании его другой переменной. Именно тогда становится важным порядок применения оператора увеличения.
Увеличение с помощью постфикса: сначала используется значение, затем происходит увеличение
«Постфикс» означает «после»: сначала выражение возвращает текущее значение переменной, а увеличение происходит позже.
let x: number = 10;
let res: number = x++;
Печать обеих переменных показывает результат действия:
console.log(res); // 10
console.log(x); // 11
Порядок событий следующий:
- Считывается текущее значение
x, равное10. - Это значение становится результатом выражения и сохраняется в
res. - Затем значение
xувеличивается до11.
Таким образом, res сохраняет старое значение 10, в то время как x принимает значение 11. Увеличение действительно происходит, но только после копирования значения.
Увеличение с префиксом: сначала прибавляем один, затем используем значение
«Pre» означает «до»: сначала переменная увеличивается, а выражение возвращает обновленное значение.
let x: number = 10;
let res: number = ++x;
Результат подтверждает это:
console.log(res); // 11
console.log(x); // 11
Здесь порядок обратный:
xменяется с10на11.- Новое значение
11сохраняется вres.
Обе переменные в итоге имеют значение 11. Кратко: x++ возвращает значение до увеличения, а ++x — значение после него.
Уменьшение следует тем же правилам
-- является аналогом ++, но выполняет вычитание вместо сложения. Вот форма с префиксом:
let x: number = 10;
let res: number = --x;
А результат её работы:
console.log(res); // 9
console.log(x); // 9
При префиксном уменьшении значение x сначала становится 9, и это новое значение сохраняется в res; поэтому оба значения выводятся как 9. Форма с постфиксом x-- действует наоборот: сначала в res попадает первоначальное значение 10, а уже затем x становится 9. Многие команды вообще избегают использования ++ и -- в более сложных выражениях, именно потому что различие между префиксной и постфиксной формами легко неправильно интерпретировать; выражение x += 1 в отдельной строке никогда не вызывает двусмысленности.
Троичный условный оператор
Оператор тернарного выбора позволяет выразить простое условие if/else в виде одного выражения. Он использует символы ? и : и является единственным оператором в JavaScript, принимающим три операнда, откуда и происходит его название. Начнем с двух чисел:
let a: number = 100;
let b: number = 200;
Затем выберем более большое из них:
let res: number = (a > b) ? a : b;
console.log(res); // 200
Выражение состоит из трех частей:
(a > b)перед символом?— это условие.a, следующий сразу за?, — это результат, когда условие истинно.b, следующий за символом:, — это результат, когда условие ложно.
Если прочитать вслух, там написано: если a больше b, использовать a, в противном случае — b. Поскольку 100 не больше 200, условие оказывается false, и res принимает значение 200.
Замена блока if/else
Основное преимущество заключается в краткости. Та же логика, записанная в виде отдельного предложения, занимает несколько строк и требует объявления res перед его присваиванием:
// Without ternary
let res: number;
if (a > b) {
res = a;
} else {
res = b;
}
Тернарная версия выполняет ту же задачу в одной строке. Эти два фрагмента являются альтернативами и не предназначены для вставки в один и тот же контекст, поскольку двойное объявление res с помощью let приведёт к ошибке:
// With ternary — same result, one line
let res: number = (a > b) ? a : b;
Поскольку тернарная конструкция является выражением, она позволяет одновременно объявить переменную и присвоить ей начальное значение, а TypeScript определяет или проверяет тип результата на основе обоих вариантов.
Сопоставление значения с меткой
Троичные операторы также эффективно работают, когда ветви имеют другой тип, чем условие. В этом примере возраст преобразуется в описательную строку:
let personage: number = 30;
let res: string = (personage > 18) ? "adult" : "minor";
console.log(res); // "adult"
30 > 18 равно true, поэтому res будет равен "adult". При возрасте 15 условие будет равно false, и res станет "minor".
Используйте троичные операторы для простых, однократных решений вроде этих. Как только вы начнете вкладывать один троичный оператор в другой для обработки нескольких случаев, читаемость быстро снижается, и цепочка if...else if...else (или объект поиска) обычно бывает понятнее.
Основные выводы
- Операторы TypeScript — это операторы JavaScript; компилированный результат ведет себя одинаково во время выполнения.
/ никогда не обрезает значение, % возвращает остаток от деления, а слишком большие результаты операции ** теряют точность, если не использовать тип bigint.+=, представляют собой сокращение для операции «вычислить и сохранить обратно».=== и !==; операторы слабого равенства преобразуют типы в неожиданных случаях.any молча устраняет это ограничение.&& и || работают по принципу краткого замыкания и возвращают один из операндов, поэтому они также могут использоваться в качестве защитных конструкций и значений по умолчанию.x++ возвращает старое значение, а префиксная форма ++x — новое; разница проявляется только тогда, когда используется результат операции.if...else.