Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Konwersja typów i operacje na typach

W Parku Jurajskim naukowcy często muszą przekształcać dane z jednej formy w drugą - tłumaczyć sekwencje DNA na atrybuty dinozaurów, konwertować pomiary między różnymi systemami czy analizować zachowania zwierząt. Podobnie w JavaScript, konwersja typów i operacje na typach danych to podstawowe umiejętności, które pozwalają na efektywne manipulowanie danymi.

Niejawna konwersja typów (Type Coercion)

JavaScript jest językiem dynamicznie typowanym, co oznacza, że automatycznie konwertuje typy danych, gdy jest to potrzebne do wykonania operacji. To jak automatyczne systemy adaptacyjne w Parku Jurajskim, które dostosowują się do zmian warunków - czasami wygodne, ale czasami zaskakujące.

Konwersja w operacjach arytmetycznych

Kiedy JavaScript wykonuje operacje arytmetyczne, próbuje automatycznie konwertować wartości na liczby:

1// String + Number -> String (konkatenacja)
2"Velociraptor " + 3;  // "Velociraptor 3"
3
4// String - Number -> Number
5"50" - 10;  // 40 (string "50" zostaje skonwertowany na liczbę)
6
7// String * Number -> Number
8"5" * 4;    // 20 (string "5" zostaje skonwertowany na liczbę)
9
10// String / Number -> Number
11"20" / 5;   // 4 (string "20" zostaje skonwertowany na liczbę)

Zauważ szczególny przypadek operatora

+
, który dla stringów działa jako operator konkatenacji (łączenia), a nie dodawania. To jak obserwacja, że velociraptory zawsze polują w stadach - wiedza, która może uratować życie!

Konwersja w operacjach porównania

Kiedy porównujemy wartości różnych typów za pomocą operatora

==
(równość nieścisła), JavaScript próbuje je skonwertować na wspólny typ:

1// Number == String
25 == "5";     // true (string "5" zostaje skonwertowany na liczbę 5)
3
4// Boolean == Number
5true == 1;    // true (true zostaje skonwertowany na 1)
6false == 0;   // true (false zostaje skonwertowany na 0)
7
8// null == undefined
9null == undefined;  // true (specjalny przypadek w specyfikacji)
10
11// Object == Primitive
12[1] == 1;     // true ([1] zostaje skonwertowany na "1", a potem na 1)

Takie zachowanie przypomina "intuicyjne" obserwacje w parku - "Ta zagroda wygląda tak samo jak tamta" - które mogą być mylące, jeśli nie znasz szczegółów.

Konwersja w operacjach logicznych

Operatory logiczne

&&
,
||
i
!
mogą pracować z wartościami każdego typu, konwertując je niejawnie na boolean:

1// Operator ! (negacja) konwertuje wartość na boolean i neguje ją
2!0;           // true (0 zostaje skonwertowane na false, a potem znegowane)
3!"Dino";      // false ("Dino" zostaje skonwertowane na true, a potem znegowane)
4
5// Operatory && i || mogą zwracać oryginalne wartości, a nie tylko boolean
6"T-Rex" && "Raptor";  // "Raptor" (jeśli pierwszy operand jest truthy, zwraca drugi)
70 || "Backup";        // "Backup" (jeśli pierwszy operand jest falsy, zwraca drugi)

Ta logika jest jak protokół postępowania w parku: "Jeśli ogrodzenie działa ORAZ jest wysokie, dinozaur nie ucieknie" - obydwa warunki muszą być spełnione, aby całe zdanie było prawdziwe.

Jawna konwersja typów (Explicit Type Conversion)

Aby uniknąć nieoczekiwanych wyników niejawnej konwersji, często warto jawnie skonwertować wartości na pożądany typ. To jak precyzyjne protokoły laboratoryjne w Parku Jurajskim - dokładnie określone procedury dające przewidywalne rezultaty.

Konwersja na String

1// Metoda #1: Funkcja String()
2String(42);         // "42"
3String(true);       // "true"
4String(null);       // "null"
5String(undefined);  // "undefined"
6String({ name: "T-Rex" });  // "[object Object]"
7String([1, 2, 3]);  // "1,2,3"
8
9// Metoda #2: Metoda toString()
10(42).toString();    // "42"
11true.toString();    // "true"
12[1, 2, 3].toString();  // "1,2,3"
13// null.toString();    // Błąd! Null i undefined nie mają metody toString()
14
15// Metoda #3: Konkatenacja z pustym stringiem
1642 + "";            // "42"
17true + "";          // "true"
18null + "";          // "null"

Konwersja na Number

1// Metoda #1: Funkcja Number()
2Number("42");       // 42
3Number("42px");     // NaN (nie można skonwertować całego stringa na liczbę)
4Number(true);       // 1
5Number(false);      // 0
6Number(null);       // 0
7Number(undefined);  // NaN
8Number([1]);        // 1 (jednolementowa tablica jest konwertowana na jej element)
9Number([1, 2]);     // NaN (tablica z wieloma elementami nie może być konwertowana)
10
11// Metoda #2: Funkcje parseInt() i parseFloat()
12parseInt("42");     // 42
13parseInt("42px");   // 42 (konwertuje, dopóki napotka nieprawidłowy znak)
14parseInt("3.14");   // 3 (konwertuje tylko część całkowitą)
15parseFloat("3.14"); // 3.14 (konwertuje część zmiennoprzecinkową)
16
17// Metoda #3: Operatory unarny + i -
18+"42";              // 42
19-"42";              // -42
20+true;              // 1

Konwersja na Boolean

1// Metoda #1: Funkcja Boolean()
2Boolean(42);        // true
3Boolean(0);         // false
4Boolean("");        // false
5Boolean("text");    // true
6Boolean(null);      // false
7Boolean(undefined); // false
8Boolean({});        // true (każdy obiekt jest truthy)
9
10// Metoda #2: Podwójna negacja !!
11!!42;               // true
12!!0;                // false
13!!"text";           // true
14!!null;             // false

Tabele konwersji

Poniższe tabele są jak tablice genetyczne dinozaurów w parku - pokazują, co dokładnie dzieje się podczas konwersji między różnymi typami.

Konwersja na String

| Typ wejściowy | Przykład wejściowy | Wynik konwersji na String | |--------------|-------------------|--------------------------| | Number | 42 | "42" | | Boolean | true | "true" | | null | null | "null" | | undefined | undefined | "undefined" | | Array | [1, 2, 3] | "1,2,3" | | Object | {name: "Rex"} | "[object Object]" | | Function | function(){} | "function(){}" |

Konwersja na Number

| Typ wejściowy | Przykład wejściowy | Wynik konwersji na Number | |--------------|-------------------|--------------------------| | String | "42" | 42 | | String | " 42 " | 42 (białe znaki są usuwane) | | String | "42px" | NaN | | String | "" | 0 (pusty string) | | Boolean | true | 1 | | Boolean | false | 0 | | null | null | 0 | | undefined | undefined | NaN | | Array | [] | 0 (pusta tablica) | | Array | [5] | 5 (tablica z jednym elementem) | | Array | [1, 2] | NaN (wiele elementów) | | Object | {} | NaN |

Konwersja na Boolean

| Typ wejściowy | Przykład wejściowy | Wynik konwersji na Boolean | |--------------|-------------------|---------------------------| | Number | 42 | true | | Number | 0 | false | | Number | NaN | false | | String | "tekst" | true | | String | "" | false | | null | null | false | | undefined | undefined | false | | Object | {} (jakikolwiek) | true | | Array | [] (jakakolwiek) | true |

Operacje na typach danych

Teraz zajmijmy się szczegółowymi operacjami na poszczególnych typach danych. To jak specjalistyczne procedury dla różnych gatunków dinozaurów.

Operacje na Stringach

Stringi w JavaScript oferują bogaty zestaw metod do manipulacji tekstem:

1const parkName = "Jurassic Park";
2
3// Długość stringa
4parkName.length;  // 13
5
6// Dostęp do poszczególnych znaków
7parkName[0];        // "J"
8parkName.charAt(0); // "J" (alternatywna metoda)
9
10// Wyszukiwanie podciągów
11parkName.indexOf("Park");      // 9 (indeks, gdzie zaczyna się "Park")
12parkName.startsWith("Jur");    // true
13parkName.endsWith("Park");     // true
14parkName.includes("assic");    // true
15
16// Zmiana wielkości liter
17parkName.toUpperCase();        // "JURASSIC PARK"
18parkName.toLowerCase();        // "jurassic park"
19
20// Wycinanie fragmentów
21parkName.substring(0, 8);      // "Jurassic"
22parkName.slice(-4);            // "Park" (liczy od końca)
23
24// Zastępowanie fragmentów
25parkName.replace("Jurassic", "Dinosaur");  // "Dinosaur Park"
26
27// Podział stringa na tablicę
28parkName.split(" ");           // ["Jurassic", "Park"]
29
30// Usuwanie białych znaków
31"  Danger Zone  ".trim();      // "Danger Zone"
32
33// Łączenie stringów
34"Welcome".concat(" to ", parkName); // "Welcome to Jurassic Park"
35
36// Powtarzanie stringa
37"Roar! ".repeat(3);            // "Roar! Roar! Roar! "

Operacje na Liczbach

JavaScript oferuje wiele możliwości pracy z liczbami, zarówno bezpośrednio na wartościach, jak i przez obiekt

Math
:

1// Podstawowe operacje arytmetyczne
25 + 3;    // 8 (dodawanie)
35 - 3;    // 2 (odejmowanie)
45 * 3;    // 15 (mnożenie)
55 / 3;    // 1.6666666666666667 (dzielenie)
65 % 3;    // 2 (modulo - reszta z dzielenia)
75 ** 3;   // 125 (potęgowanie - 5 do potęgi 3)
8
9// Inkrementacja i dekrementacja
10let dinoCount = 5;
11dinoCount++;  // dinoCount jest teraz 6
12dinoCount--;  // dinoCount z powrotem do 5
13
14// Operatory przypisania i operacji
15dinoCount += 3;  // To samo co: dinoCount = dinoCount + 3
16dinoCount *= 2;  // To samo co: dinoCount = dinoCount * 2
17
18// Obiekt Math do zaawansowanych operacji
19Math.abs(-5);            // 5 (wartość bezwzględna)
20Math.round(4.7);         // 5 (zaokrąglenie do najbliższej liczby całkowitej)
21Math.ceil(4.1);          // 5 (zaokrąglenie w górę)
22Math.floor(4.9);         // 4 (zaokrąglenie w dół)
23Math.max(3, 7, 2, 4);    // 7 (znajdowanie maksimum)
24Math.min(3, 7, 2, 4);    // 2 (znajdowanie minimum)
25Math.pow(2, 3);          // 8 (potęgowanie - 2 do potęgi 3)
26Math.sqrt(16);           // 4 (pierwiastek kwadratowy)
27Math.sin(0);             // 0 (sinus)
28Math.cos(0);             // 1 (cosinus)
29Math.random();           // Losowa liczba od 0 (włącznie) do 1 (wyłącznie)
30
31// Generowanie losowej liczby z zakresu
32function getRandom(min, max) {
33    return Math.floor(Math.random() * (max - min + 1)) + min;
34}
35getRandom(1, 10);  // Losowa liczba od 1 do 10
36
37// Formatowanie liczb
38const height = 5.678934;
39height.toFixed(2);           // "5.68" (dwa miejsca po przecinku, zwraca string)
40height.toPrecision(3);       // "5.68" (trzy znaczące cyfry, zwraca string)
41height.toString(2);          // "101.10101011100001010001111" (reprezentacja binarna)
42(1234567).toLocaleString();  // "1,234,567" (formatowanie z separatorami, zależy od locale)

Operacje na Wartościach Logicznych

Chociaż typ boolean ma tylko dwie możliwe wartości, istnieje wiele operacji logicznych:

1// Operatory porównania
25 > 3;              // true (większe niż)
35 >= 5;             // true (większe lub równe)
43 < 5;              // true (mniejsze niż)
53 <= 3;             // true (mniejsze lub równe)
65 == "5";           // true (równość nieścisła - różne typy są konwertowane)
75 === "5";          // false (równość ścisła - typy muszą być zgodne)
85 != "6";           // true (nierówność nieścisła)
95 !== "5";          // true (nierówność ścisła)
10
11// Operatory logiczne
12true && true;       // true (AND logiczne - oba warunki muszą być prawdziwe)
13true && false;      // false
14true || false;      // true (OR logiczne - co najmniej jeden warunek musi być prawdziwy)
15false || false;     // false
16!true;              // false (NOT logiczne - negacja)
17!false;             // true
18
19// Złożone warunki
20const isSafe = fenceActive && powerLevel > 5000 && !stormApproaching;
21const needsEvacuation = alarmTriggered || dinoEscaped || stormStrength > 8;

Operacje na null i undefined

Pomimo ograniczonych możliwości bezpośrednich operacji na

null
i
undefined
, istnieją użyteczne wzorce pracy z nimi:

1// Operator trójskładnikowy do domyślnych wartości
2const visitorName = userName || "Gość";  // Użycie userName, jeśli istnieje, w przeciwnym razie "Gość"
3
4// Operator nullish coalescing (??) - dostępny w nowszych wersjach JS
5const count = dinoCount ?? 0;  // Użycie dinoCount, jeśli nie jest null/undefined, w przeciwnym razie 0
6
7// Optional chaining (?.) - dostępny w nowszych wersjach JS
8const species = dinosaur?.species;  // Bezpiecznie dostęp do właściwości, nawet jeśli dinosaur jest null/undefined

Ciekawe i trudne przypadki konwersji typów

Niektóre konwersje typów w JavaScript mogą być zaskakujące, podobnie jak nietypowe zachowania dinozaurów po burzy:

1// Dziwne przypadki równości nieścisłej (==)
2[] == '';            // true (pusta tablica jest konwertowana na pusty string)
3[1] == 1;            // true (tablica [1] jest konwertowana na string "1", a potem na liczbę 1)
4[1, 2] == '1,2';     // true (tablica [1, 2] jest konwertowana na string "1,2")
5true == '1';         // true (true jest konwertowane na 1, a '1' na 1)
6false == '0';        // true (false jest konwertowane na 0, a '0' na 0)
7null == undefined;   // true (specjalny przypadek w specyfikacji)
8'' == 0;             // true (pusty string jest konwertowany na 0)
9
10// Operacje z NaN
11NaN == NaN;          // false (NaN nie jest równe niczemu, nawet samemu sobie)
12Number.isNaN(NaN);   // true (właściwy sposób sprawdzania NaN)
13
14// Operacje z obiektami
15const obj1 = {}, obj2 = {};
16obj1 == obj2;        // false (obiekty są porównywane przez referencję, nie zawartość)
17obj1 === obj2;       // false (obiekty są porównywane przez referencję, nie zawartość)
18
19// Operacje na tablicach
20[] + [];             // "" (tablice są konwertowane na stringi, a potem łączone)
21[] + {};             // "[object Object]" (obiekt jest konwertowany na string)
22{} + [];             // 0 (puste {} jest traktowane jako blok kodu, a +[] jest konwertowane na liczbę)

Najlepsze praktyki

Aby uniknąć niespodzianek przy pracy z typami danych, warto stosować następujące zasady - są jak protokoły bezpieczeństwa w Parku Jurajskim:

1. Używaj ścisłego porównania (===, !==)

1// ŹLE
2if (dinoCount == "5") {  // Zadziała, ale może prowadzić do błędów
3
4// DOBRZE
5if (dinoCount === 5) {  // Wyraźnie wskazuje, że dinoCount powinno być liczbą

2. Jawnie konwertuj typy, gdy jest to potrzebne

1// ŹLE
2const totalVisitors = visitorCount + newVisitors;  // Może dawać niepoprawne wyniki, jeśli któraś zmienna jest stringiem
3
4// DOBRZE
5const totalVisitors = Number(visitorCount) + Number(newVisitors);  // Jawna konwersja na liczby

3. Używaj odpowiednich metod sprawdzania typu

1// ŹLE
2if (typeof height == "number" && !isNaN(height)) {  // Działa, ale jest nieprecyzyjne
3
4// DOBRZE
5function isValidNumber(value) {
6    return typeof value === 'number' && !isNaN(value) && isFinite(value);
7}
8if (isValidNumber(height)) {

4. Uważaj na operacje na obiektach

1// ŹLE
2if (dinosaur == otherDinosaur) {  // Porównuje referencje, nie zawartość
3
4// DOBRZE
5function isEqual(obj1, obj2) {
6    return JSON.stringify(obj1) === JSON.stringify(obj2);  // Dla prostych obiektów
7}
8if (isEqual(dinosaur, otherDinosaur)) {

5. Używaj operatorów nullish coalescing (??) i optional chaining (?.)

1// ZAMIAST
2const safeVisitorCount = visitorCount !== null && visitorCount !== undefined ? visitorCount : 0;
3const speciesName = dinosaur && dinosaur.species ? dinosaur.species : "Nieznany";
4
5// LEPIEJ (w nowszym JS)
6const safeVisitorCount = visitorCount ?? 0;
7const speciesName = dinosaur?.species ?? "Nieznany";

Praktyczny przykład: Przetwarzanie danych o dinozaurach

Poniższy przykład pokazuje, jak zastosować operacje na typach danych w praktycznym scenariuszu:

1// Dane przychodzące z różnych źródeł (symulacja API, formularzy, plików CSV)
2const rawDinoData = [
3    { id: "1", name: "Rexy", species: "T-Rex", height: "5.6", dangerous: "true", lastFed: "2023-05-15T08:30:00" },
4    { id: 2, name: "Blue", species: "Velociraptor", height: "1.8", dangerous: true, lastFed: "2023-05-15T09:15:00" },
5    { id: "3", name: "Spike", species: "Stegosaurus", height: 4.2, dangerous: "false", lastFed: null },
6    { id: "4", name: "Cera", species: "Triceratops", height: "unknown", dangerous: false, lastFed: "invalid-date" }
7];
8
9// Funkcja do normalizacji i walidacji danych
10function processDinosaurData(dinoList) {
11    // Przygotowanie znormalizowanej tablicy
12    const processedData = [];
13    
14    // Przetworzenie każdego elementu
15    for (let i = 0; i < dinoList.length; i++) {
16        const dino = dinoList[i];
17        
18        // Normalizacja ID
19        const id = Number(dino.id);
20        if (isNaN(id) || id <= 0) {
21            console.warn(`Pominięto dinozaura z nieprawidłowym ID: ${dino.id}`);
22            continue;
23        }
24        
25        // Normalizacja imienia
26        const name = String(dino.name).trim();
27        if (name === "") {
28            console.warn(`Pominięto dinozaura bez imienia, ID: ${id}`);
29            continue;
30        }
31        
32        // Normalizacja gatunku
33        const species = String(dino.species).trim() || "Nieznany";
34        
35        // Normalizacja wysokości
36        let height;
37        if (dino.height === "unknown" || dino.height === undefined) {
38            height = null;
39        } else {
40            height = Number(dino.height);
41            if (isNaN(height)) {
42                console.warn(`Nieprawidłowa wysokość dla ${name}, ustawiono null`);
43                height = null;
44            }
45        }
46        
47        // Normalizacja flagi niebezpieczny
48        let dangerous;
49        if (typeof dino.dangerous === 'string') {
50            dangerous = dino.dangerous.toLowerCase() === 'true';
51        } else {
52            dangerous = Boolean(dino.dangerous);
53        }
54        
55        // Normalizacja daty ostatniego karmienia
56        let lastFed = null;
57        if (dino.lastFed) {
58            const date = new Date(dino.lastFed);
59            if (!isNaN(date.getTime())) {
60                lastFed = date;
61            } else {
62                console.warn(`Nieprawidłowa data karmienia dla ${name}`);
63            }
64        }
65        
66        // Dodatkowe obliczenia
67        const securityLevel = dangerous ? (height > 3 ? "Wysoki" : "Średni") : "Niski";
68        const daysFromLastFeed = lastFed ? Math.floor((new Date() - lastFed) / (1000 * 60 * 60 * 24)) : null;
69        
70        // Dodanie znormalizowanego dinozaura do wynikowej tablicy
71        processedData.push({
72            id,
73            name,
74            species,
75            height,
76            dangerous,
77            lastFed,
78            securityLevel,
79            daysFromLastFeed,
80            needsFeeding: daysFromLastFeed === null || daysFromLastFeed > 1
81        });
82    }
83    
84    return processedData;
85}
86
87// Używanie funkcji
88const normalizedDinos = processDinosaurData(rawDinoData);
89console.log(normalizedDinos);
90
91// Dodatkowe przetwarzanie danych
92const dangerousDinos = normalizedDinos.filter(dino => dino.dangerous);
93const avgHeight = normalizedDinos
94    .filter(dino => dino.height !== null)
95    .reduce((sum, dino) => sum + dino.height, 0) / normalizedDinos.length;
96const dinosBySecurityLevel = normalizedDinos.reduce((groups, dino) => {
97    groups[dino.securityLevel] = groups[dino.securityLevel] || [];
98    groups[dino.securityLevel].push(dino);
99    return groups;
100}, {});

Ten przykład pokazuje, jak radzić sobie z różnymi typami danych, konwertować je, walidować i normalizować - wszystko to są podstawowe operacje, których naukowcy w Parku Jurajskim musieliby dokonywać na codziennych danych.

Podsumowanie

Konwersja typów i operacje na typach danych w JavaScript to obszerne zagadnienie, które wymaga zrozumienia wielu niuansów. Kluczowe aspekty to:

  1. Niejawna konwersja - JavaScript automatycznie konwertuje typy w wielu kontekstach

    • Może prowadzić do nieoczekiwanych wyników
    • Jest wygodna, ale czasem niebezpieczna
  2. Jawna konwersja - Kontrolowane przekształcanie typów

    • String()
      ,
      Number()
      ,
      Boolean()
      - funkcje konwertujące
    • toString()
      ,
      parseInt()
      ,
      parseFloat()
      - metody i funkcje specjalizowane
    • !!
      - szybka konwersja na boolean
  3. Operacje na poszczególnych typach

    • Stringi mają bogaty zestaw metod do manipulacji tekstem
    • Liczby mogą być przetwarzane przy użyciu operatorów i obiektu Math
    • Wartości logiczne są podstawą dla instrukcji warunkowych
    • Null i undefined mają specjalne zasady w operacjach
  4. Najlepsze praktyki

    • Używanie ścisłego porównania (===, !==)
    • Jawne konwertowanie typów tam, gdzie to potrzebne
    • Stosowanie odpowiednich funkcji sprawdzających

Podobnie jak w Parku Jurajskim, gdzie różne gatunki dinozaurów wymagają różnych protokołów postępowania, tak w JavaScript różne typy danych wymagają odpowiedniego podejścia. Znajomość zasad konwersji i operacji na typach pozwala na skuteczne i bezpieczne manipulowanie danymi - podstawę każdej aplikacji JavaScript.

W następnej lekcji przyjrzymy się operatorom arytmetycznym i przypisania, które pozwalają na przeprowadzanie bardziej zaawansowanych kalkulacji - niezbędnych dla zarządzania zasobami w tak złożonym przedsięwzięciu jak Park Jurajski.

Przejdź do CodeWorlds