Operatorzy TypeScript w praktyce: matematyka, porównania, logika i operatory ternarne
Praktyczny przewodnik po operatorach arytmetycznych, przydzielania, porównywania, logicznych, inkrementacji oraz ternarnych w TypeScript, w tym o sprawdzaniu równości, którego kompilator odmawia.
Gdy już poznasz, w jaki sposób TypeScript przypisuje i egzekwuje typy, następnym krokiem jest wykonywanie jakichś działań z wartościami: ich dodawanie, porównywanie, łączenie warunków oraz wybieranie między alternatywami. Operatory to symbole, które wykonują te operacje, a większość z nich zachowuje się dokładnie tak samo jak w JavaScript. Niektóre jednak ukrywają szczegóły, które regularnie powodują błędy, takie jak różnica między równością luźną a ścisłą oraz różnica pomiędzy x++ a ++x.
To przewodnik omawia operatory arytmetyczne, przypisania, relacyjne, logiczne, inkrementacji i dekrementacji oraz operator ternarny, przytaczając krótkie przykłady, nad którymi można się zastanowić bez ich uruchamiania. Po drodze zobaczysz, gdzie kompilator TypeScript dodaje mechanizmy bezpieczeństwa, których brakuje w zwykłym JavaScript.
Najpierw skrót: kilka deklaracji w jednym zdaniu
Zanim przejdziemy do operatorów, warto wspomnieć o jednej wygodzie składniowej, od której zależą późniejsze przykłady. Zwykły sposób deklarowania dwóch liczb polega na użyciu jednego instrukcji na każdą zmienną:
let a: number = 10;
let b: number = 20;
Jedno słowo kluczowe let może również wprowadzić kilka zmiennych jednocześnie, jeśli oddzielimy je przecinkami:
let a: number = 10, b: number = 20;
Oba te sposoby skompilują się w to samo; drugi jest po prostu krótszy. Każda zmienna na liście ma własną adnotację, więc mogą one mieć różne typy:
let a: number = 10, name: string = "John", isActive: boolean = true;
Należy używać tego sposobu z umiarem. Jest on dobrze czytelny w przypadku par wartości ściśle powiązanych, takich jak a i b używane w tym przewodniku, ale linia łącząca licznik, nazwę i flagę jest trudniejsza do przeczytania niż trzy oddzielne deklaracje.
Operatory arytmetyczne
Są to znane symbole z matematyki szkolnej. Poniższe przykłady rozpoczynają się od dwóch liczb:
// Arithmetic operators
let a: number = 10, b: number = 20;
Każdy wiersz stosuje wtedy jeden operator i pokazuje wynik w komentarzu:
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)
Co robi każdy z nich:
+dodaje dwa operandy.-odejmuje prawy operand od lewego.*mnoży je ze sobą./dzieli je bez zaokrąglenia.10 / 20to rzeczywiście0.5; nie ma oddzielnej dzielenia całkowitego jak w niektórych innych językach, ponieważ każda liczba w JavaScript jest wartością zmiennoprzecinkową.%zwraca resztę.10 % 20to10: dwadzieścia razy nie da się wziąć dziesięciu, więc cała dziesiątka pozostaje.**podnosi lewy operand do potęgi prawego.10 ** 20to dziesięć pomnożone przez same siebie dwadzieścia razy, co tłumaczy bardzo długi wynik.
Jedna uwaga dotycząca ** i dużych wyników: liczby przekraczające Number.MAX_SAFE_INTEGER (około dziewięciu kwadrilionów) tracą precyzję całkowitą. 10 ** 20 jest wyświetlany dokładnie, ale operacje arytmetyczne na tak dużych wartościach nie są wiarygodne; użyj bigint, gdy potrzebujesz dokładnych dużych liczb całkowitych.
Przypisywanie i złożone przypisywanie
Zwykłe = przechowuje wartość w zmiennej. JavaScript i TypeScript dostarczają również operatorów złożonego przypisywania, które wykonywają operację arytmetyczną i przechowują wynik w jednym wyrażeniu. Zacznijmy od dwóch wartości:
let a: number = 100, b: number = 50;
Następnie zaktualizuj a dwa razy – raz w tradycyjny sposób, a raz za pomocą skrótu:
console.log(a); // 100
a = a + b;
console.log(a); // 150
console.log(a += b); // 200
Krok po kroku: a zaczyna od wartości 100. Wyrażenie a = a + b odczytuje aktualną wartość a, dodaje b (50) i zapisuje wartość 150 z powrotem do a. Nie ma tu nic nowego poza operatorem + omówionym w poprzedniej sekcji.
Ostatni wiersz używa a += b, co oznacza dokładnie to samo co a = a + b, tylko jest napisane w bardziej zwięzły sposób. Ponieważ a ma w tym momencie wartość 150, staje się ona 200. Przypisanie to również jest wyrażeniem, którego wartością jest nowo zapisana liczba, dlatego przekazanie a += b bezpośrednio do console.log wyświetla wartość 200. To działa, ale ukrywanie zmiany stanu wewnątrz argumentu funkcji utrudnia zrozumienie kodu, dlatego w prawdziwym kodzie lepiej wykonywać takie aktualizacje na osobnym wierszu.
Ten sam schemat obowiązuje dla każdego operatora arytmetycznego: -=, *=, /=, %= oraz **= – one odejmują, mnożą, dzielą, obliczają resztę lub wykonywają operację potęgowania, a następnie przypisują wartość.
Operatorzy relacyjne
Operatory porównawcze analizują dwa wartości i zawsze zwracają wartość logiczną, true lub false. Oto operandy:
let a: number = 10;
let b: number = 20;
A porównania:
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
Operatorzy porządkowe są oczywiste: > (większe od), < (mniejsze od), >= (większe lub równe) oraz <= (mniejsze lub równe). Operatorzy równości ==, === i != sprawdzają, czy dwa wartości są identyczne, ale == i === działają według innych zasad, a ta różnica wymaga osobnego omówienia. Ścisłym odpowiednikiem != jest !==, i to właśnie go należy zazwyczaj używać.
Równość luźna versus równość ścisła
Dwa operatorzy równości różnią się sposobem traktowania typów:
==to równość luźna. Jeśli operandy mają różne typy, jeden z nich jest cicho konwertowany przed porównaniem wartości.
=== oznacza ściśłą równość. Wymaga tego samego typu i tej samej wartości, nie przeprowadza żadnych konwersji i zwraca false, gdy typy się różnią.Aby to pokazać, weźmy liczbę i ciąg znaków, które wyglądają podobnie. Oba są celowo oznaczone jako any; powód zostanie wkrótce wyjaśniony.
let num1: any = 10;
let num2: any = "10";
Porównanie ich za pomocą poszczególnych operatorów daje różne wyniki:
console.log(num1 == num2); // true → same value, type is ignored
console.log(num1 === num2); // false → same value, but number ≠ string
num1 przechowuje liczbę 10, a num2 przechowuje ciąg znaków "10". Równość luźna przekształca ciąg znaków na liczbę, znajduje 10 po obu stronach i zwraca true. Równość ścisła całkowicie pomija tę konwersję; liczba nigdy nie może być równa ciągowi znaków, więc wynik jest false. Ponieważ zasady konwersji w przypadku równości luźnej dają zaskakujące wyniki (na przykład 0 == "" daje true), większość przewodników stylowych i narzędzi sprawdzających kod zaleca używanie === i !== we wszystkich przypadkach.
Dlaczego przykład wymaga użycia any
Adnotacje any nie są przypadkowe. Jeśli zadeklarujesz zmienne z ich rzeczywistymi typami, let num1: number = 10; i let num2: string = „10“;, a następnie napiszesz num1 === num2, kod nie zostanie skompilowany. Kompilator zgłosi błąd w przybliżeniu w tych słowach:
This comparison appears to be unintentional because the types 'number' and 'string' have no overlap.
TypeScript wie, że wartość typu number i wartość typu string nigdy nie mogą być takie same, dlatego traktuje to porównanie jako błąd. Ta kontrola dotyczy zarówno operatora ==, jak i ===. Ustawienie obu zmiennych na typ any wyłącza tę analizę, co jest jedynym powodem, dla którego demonstracja kompiluje się. Jest to mały, ale przydatny przykład systemu typów wykrywającego bezsensowne porównania przed uruchomieniem programu – czego zwykły JavaScript nigdy nie ostrzega. Pokazuje również, dlaczego użycie typu any jest kosztowne: w tajemnicy wyłącza właśnie ten rodzaj ochrony. Aby zapoznać się z alternatywami, zobacz sześć bezpiecznych zamienników typu any.
Operatory logiczne
Operatorzy logiczne łączą warunki. Istnieją trzy: && (AND), || (OR) oraz ! (NOT). Zaczynamy od dwóch wartości logicznych:
let b1 = true;
let b2 = false;
Następnie łączymy je i negujemy:
console.log(b1 && b2); // false
console.log(b1 || b2); // true
console.log(!b2); // true
console.log(!b1); // false
Pełne zachowanie operacji AND i OR dla każdej pary wartości wejściowych:
trueitrue:&&zwracatrue,||również zwracatrue.trueifalse:&&zwracafalse,||zwracatrue.falseitrue:&&zwracafalse,||zwracatrue.falseifalse:&&zwracafalse,||również zwracafalse.
A dla NOT:
!truetofalse.!falsetotrue.
Mówiąc prościej:
&&(I) jest ścisły – zwracatruetylko wtedy, gdy obie strony sątrue; jedna wartośćfalsesprawia, że całe wyrażenie jestfalse.||(LUB) jest łagodny – wystarczy jedna wartośćtrue, a zwracafalsetylko wtedy, gdy obie strony sąfalse.!(NOT) przyjmuje jeden operand i odwraca jego wartość:truestaje sięfalsei odwrotnie.
Detal, który warto znać: ! zawsze zwraca wartość logiczną, natomiast && i || faktycznie zwracają jeden z swoich operandów. W przypadku operandów logicznych jest to zawsze wartość logiczna, jak opisano powyżej. Przy innych wartościach "" || "default" zwraca "default", a user && user.name zwraca albo user, albo user.name. Obaj operatorzy działają również w trybie skróconym: jeśli strona lewa już określi wynik, strona prawa nie jest w ogóle ewaluowana. To właśnie dzięki temu zachowaniu są one często używane do ustawiania wartości domyślnych i warunków, a TypeScript odpowiednio typuje ich wynik.
Łączenie porównań z logiką
Operatorzy relacyjne i logiczne zazwyczaj występują razem: porównania tworzą wartości logiczne, a operatory logiczne łączą je ze sobą.
console.log(20 > 10 && 20 < 10); // false
console.log(20 > 10 || 20 < 10); // true
W pierwszym wierszu 20 > 10 daje wartość true, a 20 < 10 daje false. Operator AND wymaga, aby obie wartości były true, więc wynik jest false.
W drugim wierszu obie porównania są takie same, ale operator OR wymaga tylko jednej wartości true, więc wynik jest true. Ponieważ operatorzy porównania łączą się mocniej niż && i ||, nie są tu potrzebne nawiasy, chociaż ich dodanie może ułatwić czytanie bardziej złożonych warunków.
Zwiększanie i zmniejszanie
++ dodaje 1 do liczby, a -- odejmuje 1. Każdy z tych operatorów występuje w dwóch formach: przedrostkowej i tylnoprzedrostkowej, a różnica między nimi jest częstym źródłem zamieszania. Zacznijmy od pojedynczej zmiennej:
let x: number = 10;
Gdy operator jest używany samodzielnie jako instrukcja, obie formy dają ten sam efekt:
x++; // same as x = x + 1
console.log(x); // 11
++x; // same as x = x + 1
console.log(x); // 12
Napisane na osobnej linii, x++ i ++x oba po prostu dodają jedynkę do x. Te dwa formy zachowują się inaczej tylko wtedy, gdy wartość wyrażenia jest używana w tej samej instrukcji, na przykład gdy przypisuje się ją do innej zmiennej. Wtedy właśnie przydają się przedrostek i sufiks.
Zwiększanie wartości sufiksem: najpierw użyj wartości, potem dodaj jedynkę
"Sufiks" oznacza później: wyrażenie jest wyceniane na podstawie aktualnej wartości zmiennej, a zwiększenie następuje później.
let x: number = 10;
let res: number = x++;
Wyświetlenie obu zmiennych pokazuje efekt:
console.log(res); // 10
console.log(x); // 11
Kolejność zdarzeń jest następująca:
- Odczytuje się aktualną wartość
x, czyli10. - Ta wartość staje się wynikiem wyrażenia i jest przechowywana w
res. - Następnie
xjest zwiększane do11.
Zatem res zachowuje stare wartość, 10, podczas gdy x przyjmuje wartość 11. Zwiększenie rzeczywiście nastąpiło; po prostu wydarzyło się po skopiowaniu wartości.
Zwiększanie z prefiksem: dodaj jeden, a następnie użyj wartości
"Pre" oznacza przed: zmienna jest najpierw zwiększana, a wyrażenie przyjmuje wartość po aktualizacji.
let x: number = 10;
let res: number = ++x;
Wynik to potwierdza:
console.log(res); // 11
console.log(x); // 11
Tutaj kolejność jest odwrotna:
xzmienia się z10na11.- Nowa wartość,
11, jest przechowywana wres.
Oba zmienne ostatecznie mają wartość 11. Krótka wersja: x++ zwraca wartość sprzed zwiększenia, natomiast ++x – wartość po nim.
Zmniejszanie podlega tym samym zasadom
-- jest odpowiednikiem ++, ale odejmuje zamiast dodawać. Oto forma z prefiksem:
let x: number = 10;
let res: number = --x;
A jej wynik:
console.log(res); // 9
console.log(x); // 9
Przy dekrementacji z prefiksem x najpierw spada do 9, a ta nowa wartość trafia do res, więc obie zmienne wyświetlają wartość 9. Forma z postfixem x-- działałaby odwrotnie: res otrzymałby początkową wartość 10, a dopiero potem x spadłby do 9. Wiele zespołów unika używania ++ i -- w większych wyrażeniach, właśnie dlatego, że rozróżnienie między formą z prefiksem a postfixem łatwo jest błędnie zinterpretować; x += 1 na osobnej linii nigdy nie jest dwuznaczne.
Operator warunkowy trywialny
Operator ternarny wyraża prostą opcję if/else jako jedną wyrażenie. Wykorzystuje ? i :, i jest jedynym operatorem w JavaScript, który przyjmuje trzy operandy – stąd pochodzi jego nazwa. Zacznijmy od dwóch liczb:
let a: number = 100;
let b: number = 200;
Następnie wybierzmy tę większą:
let res: number = (a > b) ? a : b;
console.log(res); // 200
Wyrażenie składa się z trzech części:
(a > b)przed?to warunek.a, bezpośrednio po?, to wynik, gdy warunek jesttrue.b, po:, to wynik, gdy warunek jestfalse.
Czytając na głos, widnieje tam napis: jeśli a jest większe od b, użyj a, w przeciwnym razie użyj b. Ponieważ 100 nie jest większe od 200, warunek wynosi false, a res przyjmuje wartość 200.
Zastępowanie bloku if/else
Główną zaletą jest zwięzłość. Ta sama logika zapisaana jako instrukcja zajmuje kilka wierszy i wymaga deklaracji res przed przypisaniem mu wartości:
// Without ternary
let res: number;
if (a > b) {
res = a;
} else {
res = b;
}
Wersja ternarna realizuje to w jednym wierszu. Obie wersje są alternatywami, a nie kodem do wklejenia w ten sam kontekst, ponieważ dwukrotne deklarowanie res za pomocą let spowodowałoby błąd:
// With ternary — same result, one line
let res: number = (a > b) ? a : b;
Ponieważ wyrażenie ternarne jest również wyrażeniem, umożliwia ono jednoczesną deklarację zmiennej i jej inicjalizację, a TypeScript wywnioskowuje lub sprawdza typ wyniku na podstawie obu gałęzi.
Przyporządkowanie wartości do etykiety
Wyrażenia ternarne działają równie dobrze, gdy gałęzie mają inny typ niż warunek. Ten przykład przekształca wiek na opisowy ciąg znaków:
let personage: number = 30;
let res: string = (personage > 18) ? "adult" : "minor";
console.log(res); // "adult"
30 > 18 jest true, więc res to "dorosły". Przy wieku 15 warunek byłby false, a res to "nieletni".
Używaj wyrażeń ternarnych do prostych, pojedynczych decyzji takich jak te. Gdy zaczniesz układać jedno wyrażenie ternarne wewnątrz drugiego, aby obsłużyć kilka przypadków, czytelność szybko spada – wtedy łańcuch if...else if...else (lub obiekt wyszukiwania) jest zazwyczaj jaśniejszy.
Główne wnioski
- Operatorzy TypeScript są tymi samymi co w JavaScript; skompilowany kod zachowuje się identycznie podczas wykonywania.
/ nigdy nie skraca wartości, % zwraca resztę, a bardzo duże wyniki uzyskane za pomocą ** tracą precyzję, chyba że użyjesz typu bigint.+=, są skrótem dla operacji „oblicz i zapisz z powrotem”.=== i !==; luźna równość przekształca typy w zaskakujący sposób.any w tajemnicy usuwa tę ochronę.&& i || działają w trybie skróconym i zwracają jeden z operandów, dlatego mogą służyć zarówno jako mechanizmy ochronne, jak i wartości domyślne.x++ zwraca staryą wartość, a prefiks ++x nową; różnica jest widoczna tylko wtedy, gdy używa się wyniku.if...else.Literatura pokrewna
- JavaScript Równość Wyjaśniona: ===, ==, Operatorzy Relacyjne i Object.is — Dowiedz się, jak ścisła i luźna równość, porównania relacyjne oraz Object.is faktycznie określają tożsamość w JavaScript, a także jakie pułapki wynikające z przymusowej konwersji każdy z nich kryje.
- Podstawy pętli w TypeScript: while, do-while, for, break i continue — Poznaj, w jaki sposób pętle while, do-while i for w TypeScript oceniają swoje warunki, dlaczego zmiana zakresu zmiennych pętli wpływa na wyniki oraz jak break i continue zmieniają bieg wykonywania kodu.
- Vue 3 w praktyce: Composables, typowane kontrakty i stan proporcjonalny — Jak API kompozycyjne Vue 3, typowane właściwości i wyzwolenia sygnałów, Pinia oraz stopniowe wdrażanie umożliwiają aplikacji rozwój tylko do stopnia złożoności, jaki jest konieczny, oraz w sytuacjach, gdy Vue nie jest odpowiednim wyborem.
- Podstawy pętli w TypeScript: while, do-while, for, break i continue — Dowiedz się, w jaki sposób pętle while, do-while i for w TypeScript oceniają swoje warunki, dlaczego zasięg zmiennej pętli wpływa na wyniki oraz jak instrukcje break i continue zmieniają bieg programu.
- Te wzorce TypeScript, które zamieniają błędy w czasie wykonywania na błędy kompilacji — Naucz się dziesięciu technik TypeScript, od zjednoczeń dyskryminowanych i funkcji satisfies po typy branded oraz infer, które pozwalają kompilatorowi odrzucić nieważne stany przed wypuszczeniem kodu.