Typy danych

W Javascript dane dzielą się na 2 typy: typy proste oraz referencyjne.

typy danych w JS

Typy proste

Dane typu prostego reprezentują proste typy danych - liczby, teksty, wartości boolowskie (prawda/fałsz), niezdefiniowane (undefined) oraz null.


//przykład danych typu prostego
const varNr = 20; //prosta liczba
const text = 'To jest tekst'; //prosty łańcuch znaków
const bol = true; //logiczny - prawda/fałsz
const myNul = null; //nic

const x; //zmienna z nieokreśloną wartością, lub w ogóle nie istniejąca

Powyższe typy danych jak sama nazwa wskazuje są prostymi bytami, które poza wartością nic w sobie nie mają. Żeby wykonywać na nich jakieś operacje (np. pobrać długość tekstu) musiałbyś albo za każdym razem używać na nich osobnych funkcji które konwertują proste byty na coś bardziej złożonego (czytaj obiekty), które dawały by dostęp do odpowiednich metod i właściwości.

W JS nie musimy czegoś takiego robić. Javascript ułatwia nam pracę i gdy chcemy zrobić jakąś rzecz na typie prostym (pobrać długość tekstu, zamienić liczbę na zmienno przecinkową itp.) w tle dokonuje automatycznej tymczasowej konwersji typu prostego na obiekt, odpala użytą właściwość lub metodę, a następnie przywraca daną zmienną do typu prostego:


    const ourText = 'Przykładowy tekst'; //deklarujemy prostą zmienną
    ourText.length //js poza sceną skonwertuje ourText na obiekt String, zwróci jego długość i przywróci zmienną do typu prostego

    const nr = 23;
    nr.toFixed(2); //znowu to samo działanie - numer został na chwilę zamieniony na obiekt, została użyta metoda toFixed po czym przywrócono go do typu prostego

Dzięki powyższej konwersji w javascript wszystko zachowuje się jak obiekt. Zasada ta nie dotyczy się tylko undefined i null, które nie potrzebują mieć właściwości i metod.

Powyższe wymienione typy mają też odpowiedniki w konstruktorach, które od razu robią z nich pełnoprawne obiekty:


const varNr = new Number(20); //liczba
const text = new String('To jest tekst'); //łańcuch znaków
const varBol = new Boolean(true); //logiczny - prawda/fałsz

Czym są konstruktory i z czym to się je nauczysz się później. W powyższych przypadkach nie zaleca się stosować konstruktorów do tworzenia takich obiektów. Chcesz utworzyć tekst, napisz po prostu w cudzysłowach. Chcesz utworzyć liczbę, napisz po prostu liczbę.


const varA = "lorem ipsum";
const varB = new String("lorem ipsum"); //tutaj chciałem zaszpanować i użyłem konstruktora

console.log( varA.length ); //11
console.log( varB.length ); //11

console.log( typeof varA ); //string - prawidłowo
console.log( typeof varB ); //object - niby prawidłowo, ale...
    

Zmienne typu prostego charakteryzują się tym, że ich wartości są przypisane bezpośrednio do zmiennych (są oddzielnymi bytami). Zobaczmy przykład:


let ourVar1 = 12;
let ourVar2 = ourVar1; //do zmiennej ourVar2 przypisujemy wartość 12

ourVar1 = 15; //zmieniam tylko ourVar1

console.log(ourVar1); //15
console.log(ourVar2); //12

Powyższe równanie zachowuje się zupełnie tak, jak nas uczyli na matematyce. Czyli taki totalny klasyk.

Typy złożone

Wszystkie zmienne nie będące typem prostym są obiektami i są typu referencyjnego.
Ten typ danych charakteryzuje się tym, że zmienne nie mają przypisanej bezpośrednio wartości, a tylko wskazują na miejsce w pamięci, gdzie te dane są przetrzymywane.


let arr1 = [1, 2, 3];
let arr2 = arr1; //zmienna arr2 wskazuje na tablicę [1, 2, 3]

arr1.push(4);
console.log(arr1); //1, 2, 3, 4
console.log(arr2); //1, 2, 3, 4

W powyższym przykładzie po zmianie jednej zmiennej, zmieniają się obie. Dzieje się tak właśnie dlatego, że obie zmienne wskazują na ten sam obiekt. Żeby zadziałało to tak jak w typach prostych, druga zmienna musiałaby mieć kopię tablicy.


let arr1 = [1, 2, 3];
let arr2 = arr1.slice(); //kopiuje całą tablicę za pomocą metody slice()

arr1.push(4);
console.log(arr1); //1, 2, 3, 4
console.log(arr2); //1, 2, 3

Możliwe, że w tej chwili masz tak zwany "mindfuck". Spokojnie - z czasem wszystkie te rzeczy staną się dla ciebie automatem i nawet nie będziesz się za bardzo nad tym zastanawiał.

Podsumowując:
Typy proste są proste. Mają wartość przypisaną do siebie. To co nie jest typem prostym jest obiektem. Zmienne obiektowe nie mają przypisanej bezpośrednio wartości, a wskazują tylko na miejsce w pamięci. Przy pracy z typami prostymi za naszymi plecami są one konwertowane na bardziej złożone obiekty, a zaraz potem znowu przywracane na typy proste. Uff...

Sprawdzanie typu danych

Do sprawdzenia typu danych korzystamy z instrukcji typeof:


const num = 10;
const str = 'przykładowy tekst';
const arr = [];
const obj = {};
const nul = null;
//zmiennej und specjalnie nie zadeklarowałem

//wypisujemy typy zmiennych
console.log( typeof num ); //"number"
console.log( typeof str ); //"string"
console.log( typeof arr ); //"object" hmm?
console.log( typeof obj ); //"object"
console.log( typeof und ); //"undefined"
console.log( typeof nul ); //"object" hmm?

//sprawdzamy typy zmiennych
if (typeof num === "number") {...}
if (typeof str === "string") {...}
if (Array.isArray(arr)) {...}
if (typeof obj === "object") {...}
if (typeof und === "undefined") {...}
if (nul === null) {...}

Zwróć uwagę, jaki typ zwróciła tablica. Tak! Tablica w JS też jest obiektem.
Żeby sprawdzić jej typ, albo musielibyśmy sprawdzać jej konstruktor (poznasz to później), albo skorzystamy z zapisu Array.isArray(tab). Więcej na ten temat możesz dowiedzieć się tutaj.
Inną kluczową rzeczą którą powinieneś tutaj zapamiętać to sposób sprawdzania nieistniejących zmiennych. Nie raz będziesz tego używał.

Podobne "dziwy" dzieją się dla typu null, który w js zwraca "object". Jest to błąd javascriptu. Na szczęście bardzo to nie przeszkadza, bo wystarczy taką zmienną przyrównać do null. W końcu null to null...

Automatyczna konwersja typów

JavaScript nie wymaga od ciebie abyś deklarował typ zmiennych. Przykładowo możesz utworzyć zmienną typu liczbowego o nazwie np. someVar, a następnie przypisać jej wartość znakową:


let someVar = 10;
someVar = "to jest napis";

Z jednej strony jest to fajne, bo upraszcza sprawę. Z drugiej może powodować błędy w bardziej skompilowanych skryptach. Dlatego też w każdym większym języku (np C++) konieczne jest określanie typów danych. Dlatego też powstały dla JS takie nakładki jak TypeScript

JS lubi być automatyczny. Tak jest w przypadku operacji na typach prostych i kilku innych momentach (np. w przypadku hoistingu zmiennych czy deklaracji funkcji).
Ta automatyczność przejawia się tym, że w wielu momentach JS chce nam ułatwić życie automatycznie konwertując danych tym zmiennej na inny. Przykładowo dodając liczbę do stringa, liczba zostanie skonwertowana na string. Podobnie dodając string do tablicy (ale nie pushując do niej) tablica zostanie skonwertowana na string.

JS nie jest w stanie dodać do siebie tablic czy obiektów (bo nie robi się tego +, a specjalnymi metodami), ale potrafi dodawać numery czy stringi do siebie. Dlatego podczas operacji często stara się skonwertować "niedodawalne" typy danych na typy, które potrafi dodać - najczęściej są to stringi, ale czasami też numbery czy inne wymagane w danej sytuacji typy danych.

Poniżej zamieszczam przykłady konwersji, które w rezultacie dają string. Żeby je sprawdzić realnie, możesz wpisać je w konsoli debugera.


"kot" + "kot" //"kotkot"
20 + 1  //21
"20" + 1  //"201"
"kot" + 20  //"kot20"
[1,2,3] + "kot" //1,2,3kot, bo tablica została skonwertowana na 1,2,3
[] + "kot"  //"kot", bo tablica została skonwertowana na ""
[]  + []  //"", bo obie tablice zostały skonwertowane na "" czyli mamy "" + ""
[] + false  //"false"
23 + "" + false  //"23false"
"" + {}  //"[object Object]" bo obiekt został skonwertowany na zapis [object Object]
[1,2,3] + {}  //1,2,3[object Object]
{} + {}  //[object Object][object Object]
"23" + [1,2,3] + {} + true + false + !true  //"231,2,3[object Object]truefalsefalse"

Ale nie tylko na stringi dokonywana jest automatyczna konwersja. Czasami lepszym wyborem będzie number:


23 + true  //24, bo true zostało skonwertowane na 1
true + true + true  //3
true + false  //1, bo false zostało skonwertowane na 0
true + {}  //"true[object Object]" - konwersja true na 1 i tak by nic nie dała, bo 1 do obiektu nie da się dodać. Dlatego zostało skonwertowane na "true"
23 + 2 * []  //23 - bo tablica została skownerowana na 0

Podobnych smaczków w JS jest więcej. O ciekawych przypadkach dowiesz się z artykułu pod linkiem: https://kot-zrodlowy.pl/javascript/2018/07/17/absurdy-js.html.

Manualna konwersja na liczby

W przypadku dodawania typów złożonych (tablice, obiekty) raczej nie będziemy chcieli wykonywać operacji takich jak powyżej.

W przypadku dokonywania do siebie kilku liczb chcielibyśmy mieć pewność, że dane zmienne są liczbami, a nie stringiem.

Żeby dokonać takiej konwersji, skorzystamy z jednej z dostępnych do tego metod:


Number(str); //konwertuje tekst na liczbę

parseInt(str, system_liczbowy); //parsuje tekst na liczbę całkowitą
parseFloat(str); //parsuje tekst na liczbę

Powyższe metody różnią się tym, że parseInt i parseFloat próbują skonwertować tekst, który może w sobie zawierać litery (ale który musi się zaczynać od liczb).
W przypadku funkcji Number str może zawierać tylko liczby:


console.log( Number("100")); //100
console.log( Number("50.5")); //50.5
console.log( Number("50px")); //NaN

console.log( parseInt("24px", 10)); //24
console.log( parseInt("26.5", 10)); //26
console.log( parseInt("100kot", 10)); //100
console.log( parseInt("Ala102", 10)); //NaN - zaczyna się od liter
console.log( parseInt("Hello", 10)); //NaN

console.log( "20px" + "20px"); //20px20px
console.log( parseInt("20px", 10) + parseInt("20px", 10) + "px"); //40px

Ważne jest, by przy wykorzystaniu metody parseInt podawać system na jaki chcemy konwertować (drugi parametr). Najczęściej będzie to system 10. Jeżeli tego nie zrobimy, czasami wyniki mogą nas zaskoczyć:


console.log( parseInt("0x200")); //512
console.log( parseInt("0x200", 10)); //0

Istnieją też inne, mniej znane metody konwersji tekstu na liczby:


console.log( +"20" ); //20
console.log( "20" * 1 ); //20
console.log( "20" / 1 ); //20
console.log( ~~"20" ); //20

Trening czyni mistrza

Poniżej zamieszczam kilka zadań, które w ramach ćwiczenia możesz wykonać:

  1. Wymień z pamięci wszystkie typy proste i referencyjne występujące w js.
  2. Stwórz zmienne każdego typu. Wypisz w konsoli ich typy. Spróbuj wykonać na nich proste działania dodawania, odejmowania, mnożenia i dzielenia.
    
                let nr = 23;
                let txt = "ala ma kota";
                let undef;
                let nul = null;
                let obj = {};
    
                console.log(nr + txt);
                console.log(nr + undef);
                console.log(nul + obj);
                ...
                
  3. Masz przykładową zmienną:
    let width = "20px";
    Dodaj do niej wartość 30. Wynik wypisz w konsoli.
    
                let width = "20px";
                let sum = parseInt(width, 10) + 30;
                console.log(sum);
                
  4. Sprawdź czy zmienna age istnieje. Jeżeli nie istnieje, stwórz taką zmienną i wypisz ją w konsoli.
    
                if (typeof age === "undefined") {
                    let age = 23;
                    console.log(age);
                }