Головна / Статті / Оператори TypeScript на практиці: математика, порівняння, логіка та тернарні оператори

Оператори TypeScript на практиці: математика, порівняння, логіка та тернарні оператори

Практичний огляд операторів арифметичних, присвоєння, порівняння, логічних, інкременту та тернарних у TypeScript, включаючи перевірку рівності, яку компілятор відхиляє.

2635 слів

Як тільки ви зрозумієте, як 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.

    Логічні оператори

    Логічні оператори поєднують умови. Їх є три: && (AND), || (OR) та ! (NOT). Почніть з двох логічних значень:

    let b1 = true;
    let b2 = false;
    

    Потім поєднайте їх та заперечте:

    console.log(b1 && b2); // false
    console.log(b1 || b2); // true
    console.log(!b2);      // true
    console.log(!b1);      // false
    

    Повна поведінка операторів AND та OR для кожної пари вхідних даних:

    • true та true: && дає true, || також дає true.
    • true та false: && дає false, || дає true.
    • false та true: && дає false, || дає true.
    • false та false: && дає false, || також дає false.

    А для NOT:

    • !true дорівнює false.
    • !false дорівнює true.

    У словах:

    • && (AND) є суворим. Він повертає true лише тоді, коли обидві сторони дорівнюють true; один false робить всю виразність false.
    • || (OR) є більш лояльним. Достатньо однієї 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. Оператор AND вимагає, щоб обидві умови були true, тому результат є false.

    У другому рядку обидві умови однакові, але оператор OR потребує лише однієї 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
    

    Порядок подій є таким:

    1. Читається поточне значення x, тобто 10.
    2. Це значення стає результатом виразу та зберігається у res.
    3. Потім x збільшується до 11.

    Отже, res зберігає старе значення 10, тоді як x отримує значення 11. Збільшення справді відбулося; воно просто сталось після копіювання значення.

    Збільшення з префіксом: додати одиницю, а потім використати значення

    „Pre“ означає „до“: спочатку змінна збільшується, а вираз оцінюється за оновленим значенням.

    let x: number = 10;
    let res: number = ++x;
    

    Результат підтверджує це:

    console.log(res); // 11
    console.log(x);   // 11
    

    Тут порядок змінений:

    1. x переходить з 10 на 11.
    2. Нове значення 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
    

    Вираз складається з трьох частин:

    1. (a > b) перед ? — це умова.
    2. a, яка йде безпосередньо після ?, — це результат, коли умова є true.
    3. b, яка знаходиться після :, — це результат, коли умова є false.

    Якщо прочитати вголос, там сказано: якщо 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.