# Typy danych i konwersja

W JavaScript dane możemy podzielić na dwie grupy: typy **proste** oraz **złożone**.

#### Typy prymitywne

1. Number
2. String
3. BigInt
4. Boolean
5. Undefined
6. Null
7. Symbol

#### Typy złożone

1. Object (w tym Array, Map i Set)

## Typy prymitywne <a href="#typy-proste" id="typy-proste"></a>

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

```js
let nr = 20; //prosta liczba
let text = "To jest tekst"; //prosty łańcuch znaków
let bol = true; //logiczny - prawda/fałsz
let myNul = null; //nic
let x; //zmienna z nieokreśloną wartością
```

Powyższe typy danych jak sama nazwa wskazuje są prostymi bytami, które poza wartością nic w sobie nie mają.

Żeby móc wykonywać na nich jakieś operacje (np. pobrać długość tekstu, zwrócić tekst pisany dużymi literami itp), powinieneś móc odpalać dla nich jakieś funkcje, czy pobierać właściwości (np. length, która zwraca długość tekstu).

W językach programowania stosuje się zazwyczaj dwa podejścia. W niektórych z nich dla każdej takiej operacji musimy używać oddzielnych funkcji:

```php
//w PHP
$txt = "Ala ma kota";
strlen($txt); //długość tekstu
strtoupper($txt); //tekst pisany dużymi literami
```

W Javascript wprowadzono inne rozwiązanie, które pozwala nam używać typów prymitywnych tak jakby były obiektami, dzięki czemu metody i właściwości możemy odpalać po kropce:

```js
//prosty obiekt
const ob = {
    name : "Karol"
}
ob.name //pobieram właściwość name obiektu -> Karol


//to samo mogę zrobić dla prymitywnego tekstu
const txt = "Ala ma kota";
txt.length //długość
txt.toUpperCase(); //na duże litery
```

Żeby typy proste mogły być używane tak samo jak obiekty, Javascript musi nam ułatwić zadanie. Dla każdego typu prostego w Javascript istnieje specjalny konstruktor, za pomocą którego możemy tworzyć obiekt o danym typie (np. String(), Number(), Boolean()).

Gdy tworzymy naszą zmienną jest ona prymitywna. Gdy wywołujemy dla niej jakąś metodę lub pobieramy jakąś właściwość, Javascript za pomocą wspomnianych konstruktorów zamienia w tle naszą zmienną na odpowiedni obiekt, wykonuje dla niego daną rzecz zwracając wynik, a następnie naszą zmienną przywraca do początkowego prymitywnego stanu.

```js
const ourText = "Przykładowy tekst"; //deklarujemy prostą stałą
ourText.length //js poza sceną tworzy 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  Number, została użyta metoda toFixed po czym przywrócono go do typu prostego
```

Zasada ta nie dotyczy się tylko undefined i null, które nie potrzebują mieć właściwości i metod.

## Typy złożone <a href="#typy-zlozone" id="typy-zlozone"></a>

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

```js
let a = 12;
let b = a;

console.log(a); //12
console.log(b); //12

a = 15;

console.log(a); //15
console.log(b); //12
```

Powyższe równanie zachowuje się zupełnie tak, jak nas uczyli na lekcjach matematyki.

Wszystkie zmienne nie będące typem prymitywnym są obiektami (typami złożonymi).\
Charakteryzują się one tym, że zmienne nie mają przypisanej bezpośrednio wartości, a tylko wskazują na miejsce w pamięci, gdzie te dane są przetrzymywane.

```js
const a = [1, 2, 3];
const b = a;

a.push(4);

console.log(a); //[1, 2, 3, 4]
console.log(b); //[1, 2, 3, 4]
```

```js
const a = { name : "kot" }
const b = a;
const c = b;
const d = c;
d.name = "pies";

console.log(a, b, c, d); //{ name : "pies" } { name : "pies" } { name : "pies" } { name : "pies" }
```

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. Najprościej zapamiętać to analogią do życia codziennego. Autora kursu niektórzy nazywają Marcin, inni Doman, a jeszcze inni nazywali Głupek. To tylko inne nazwy wskazujące na ten sam bardzo fajny byt (☞ﾟヮﾟ)☞

Kolejną charakterystyczną cechą rozróżniającą typy prymitywne od obiektów jest to, że te pierwsze w przeciwieństwie do obiektów są niemutowalne. Mutowalnymi określamy takie dane, którym w czasie ich istnienia możemy zmieniać części składowe:

```js
const tab = [1, 2, 3, 4]; //tablica - obiekt złożony
tab[0] = "kot";
console.log(tab); //["kot", 2, 3, 4]; //zmieniłem pierwszy klucz
```

```js
//typ prosty
const txt = "Ala ma kota";
txt[0] = "O"; //spróbuję zmienić pierwszą literę
console.log(txt); //Ala ma kota
```

Dla nas oznacza to tyle, że przy pracy z typami prostymi działamy na tym co zwróci dana funkcja, a nie modyfikujemy bezpośrednio danej wartości:

```js
const txt = "ala";
const big = txt.toUpperCase(); //funkcja toUpperCase() zwróciła tekst pisany dużymi literami

console.log(big); //"ALA"
console.log(txt); //"ala"
```

## Literały <a href="#literaly" id="literaly"></a>

Literały to wizualny zapis wartości.

```js
const txt = "Ala ma kota";
const nr = 23;
const bool = true;
const tab = [1, 2, 3];
const ob = {}
```

W powyższym kodzie literałami są `"Ala ma kota"`, `23`, `true`, `[1, 2, 3]` czy `{}`. Wszystkie podstawiliśmy pod jakieś zmienne. Nie zawsze tak będziemy robić, bo literałów możemy używać także w wielu momentach bez dodatkowych zmiennych:

```js
console.log("Ala" + " ma kota");
[1, 2, 3].push(4);
console.log({name: "Piotr"});
```

Powyższe typy danych moglibyśmy też tworzyć za pomocą specjalnych konstruktorów:

```js
const txt = new String("Ala ma kota");
const nr = new Number(23);
const bool = new Boolean(true);
const tab = new Array(1, 2, 3);
const ob = new Object();
```

W większości przypadków nie jest to jednak zalecane. Po pierwsze wydłuża zapis, ale i powoduje, że zmienne tworzone w taki sposób tracą swój "naturalny" typ - co w niektórych sytuacjach może powodować nieoczekiwane rezultaty. Ale o tym za chwilę...

## Sprawdzanie typu danych <a href="#spradzanie_typu" id="spradzanie_typu"></a>

Do sprawdzenia typu danych korzystamy z instrukcji `typeof`:

```js
const nr = 10;
const txt = "przykładowy tekst";
const arr = [1, 2, 3];
const ob = {};
const n = null;
//zmiennej zzz specjalnie nie zadeklarowałem

console.log( typeof nr ); //"number"
console.log( typeof txt ); //"string"
console.log( typeof arr ); //"object" hmm?
console.log( typeof ob ); //"object"
console.log( typeof n ); //"object" hmm?
console.log( typeof zzz ); //"undefined"

//sprawdzamy typy zmiennych
if (typeof nr === "number") {...}
if (typeof txt === "string") {...}
if (Array.isArray(arr)) {...}
if (typeof ob === "object") {...}
if (n === null) {...}
if (typeof zzz === "undefined") {...}
```

Zwróć uwagę, jaki typ zwróciła tablica gdy sprawdziliśmy jej typ. Tak! Tablice w JavaScript też są obiektem. Żeby realnie sprawdzić, czy dana zmienna jest tablicą musimy posłużyć się instrukcją:

```js
const arr = [1, 2, 3];
Array.isArray(arr); //true
```

Podobnie jest dla typu null, który w js zwraca `"object"`. Jest to jedna z dziwnych rzeczy [w Javascript](https://stackoverflow.com/questions/18808226/why-is-typeof-null-object). Na szczęście bardzo to nie przeszkadza, bo wystarczy taką zmienną przyrównać do null. W końcu null to null...

Wracając na chwilę do tworzenia zmiennych za pomocą konstruktorów.

Na co dzień metoda ta nie jest zalecana, na rzecz tworzenia zmiennych za pomocą literałów. Tworzenie zmiennych za pomocą konstruktorów sprawia, że zawsze są typu object, co może przynieść nieoczekiwane rezultaty:

```js
const txt = new String("Ala ma kota");
const nr = new Number(23);
const bool = new Boolean(true);
const tab = new Array(1, 2, 3);
const ob = new Object();

console.log(typeof txt); //"object"
console.log(typeof nr); //"object"
console.log(typeof bool); //"object"
console.log(typeof tab); //"object"
console.log(typeof ob); //"object"
```

```js
const txt1 = "Ala"; //za pomocą literału
const txt2 = new String("Ala"); //za pomocą konstruktora

console.log(typeof txt1); //"string"
console.log(typeof txt2); //"object"
```

```js
const txt1A = "Ala";
const txt1B = "Ala";
console.log(txt1A === txt1B); //true

const txt2A = new String("Ala");
const txt2B = new String("Ala");
console.log(txt2A === txt2B); //false
```

## Operacje na różnych typach <a href="#operacje-na-roznych-typach" id="operacje-na-roznych-typach"></a>

JavaScript lubi być automatyczny. Ta automatyczność przejawia się tym, że w wielu momentach JavaScript chce nam ułatwić życie - np. automatycznie konwertując dany typ zmiennej na inny jeżeli zajdzie taka potrzeba.

Zacznijmy od dodawania do siebie tekstów (tak zwanej konkatenacji).

Jeżeli podczas takiej operacji któraś ze składowych równania (tak zwanych operandów) jest tekstem, reszta operandów stanie się tekstem:

```js
"1" + 2 //"12"
2 + "1" //"21"
```

Przy takiej konwersji brane są pod uwagę dwa kolejne operandy:

```js
2 + 3 + "4" //"54" ponieważ 2+3=5 czyli 5 + "4"
2 + 2 + 3 + "3" //"73"
2 + 2 + "3" + 3  //"433"
```

Powyższa konwersja działa tylko w przypadku dodawania. Każde inne równanie zawsze będzie konwertować operand na liczbę:

```js
"2" * 3 //6
3 * "3" //9
"5" - 1 //4
8 / "2" //4
```

W przypadku dodawania (konkatenacji) innych typów do string konwertowane są one na tekst.

Większość typów prostych stanie się tutaj słownym zapisem wartości - `"23"`, `"true"` czy `"false"`, tablice konwertowane są na zapis `"a,b,c"` a obiekty na `"[object Object]"`:

```js
[1, 2, 3] + "kot" //1,2,3kot, bo tablica została skonwertowana na 1,2,3
23 + "" + false  //"23false"

[] + "kot"  //"kot", bo tablica została skonwertowana na ""
[]  + []  //"", bo obie tablice zostały skonwertowane na "" czyli mamy "" + ""
[] + false  //"false"

"" + {}  //"[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  //"231,2,3[object Object]false"

null + null //0
null + true //1
null + true + "10" //"110"
```

Powyższe równania powinieneś traktować tylko jako ciekawostkę, albo pożywkę dla memów. W normalnej pracy takich rzeczy robić nie powinieneś, ponieważ takie typy danych dodajemy inaczej ([1](https://kursjs.pl/kurs/super-podstawy/tablice#concat), [2](https://kursjs.pl/kurs/obiekty/obiekty-duplikowanie#spread)).

Jeżeli żaden z operandów nie jest tekstem, wtedy Javascript spróbuje skonwertować dane typy na liczbę, chyba że będzie to niemożliwe:

```js
23 + true  //24, bo true zostało skonwertowane na 1
true + true + true  //3
true + false  //1, bo false zostało skonwertowane na 0
23 + 2 * []  //23 - bo tablica została skonwertowana na "". 2 * "" daje w wyniku 2 * 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"
null + true + {} //"1[object Object]"
```

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

<figure><img src="https://kursjs.pl/kurs/super-podstawy/thanks_javascript.png" alt=""><figcaption></figcaption></figure>

## Ręczna konwersja danych <a href="#konwersja-danych" id="konwersja-danych"></a>

Odpowiednie dane możemy też konwertować ręcznie.

Do konwersji danych na string, możemy posłużyć się kilkoma technikami:

```js
const nr = 102;

console.log("" + nr); //"102"
console.log(nr.toString()); //"102"
console.log(String(nr)); //"102"
```

W przypadku konwersji typów prostych na string w większości przypadków dostajemy wartość w formacie tekstowym:

```js
String(102); //"102"
String(true); //"true"
String(null); //"null"
String(undefined); //"undefined"
```

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

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

| Number(str)                   | konwertuje tekst na liczbę        |
| ----------------------------- | --------------------------------- |
| parseInt(str, systemLiczbowy) | 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:

```js
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
```

Jeżeli przy użyciu `parseInt()` nie podamy drugiego parametru będącego systemem na który konwertujemy, Javascript spróbuje wykryć go automatycznie na podstawie:

* Przyjmie system 16, jeśli łańcuch wejściowy zaczyna się od "0x".
* Przyjmie system 8 jeśli łańcuch wejściowy rozpoczyna się od "0". Ta cecha jest wycofywana.
* Przyjmie system 10 jeśli łańcuch zaczyna się od jakiejkolwiek innej wartości.

```js
console.log( parseInt("230") ); //230
console.log( parseInt("230abc") ); //230
console.log( parseInt("0x200") ); //512
console.log( parseInt("0x200", 10)); //0
```

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

```js
const txt = "20"
console.log( +txt ); //20
console.log( txt * 1 ); //20
console.log( txt / 1 ); //20
console.log( ~~txt ); //20
```

Dodatkowo do zamiany liczb możemy wykorzystać dwie dodatkowe funkcje:

| toFixed(digits)     | zwraca tekst będący liczbą zapisaną z daną precyzją. Parametr digits określa liczbę cyfr po przecinku                                                                         |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| toPrecision(digits) | bardzo podobna do powyższej. Główną różnicą jest parametr digits, który określa liczbę cyfr w całym zapisie. Opcjonalny parametr digits dodatkowo musi być z przedziału 1-100 |

```js
const nr = 123.456789;

nr.toFixed()      // "123"
nr.toFixed(0)     // "123"
nr.toFixed(1)     // "123.5"
nr.toFixed(2)     // "123.46"
nr.toFixed(3)     // "123.457"
nr.toFixed(4)     // "123.4568"
nr.toFixed(5)     // "123.45679"
nr.toFixed(6)     // "123.456789"
nr.toFixed(7)     // "123.4567890"
nr.toFixed(8)     // "123.45678900"
nr.toFixed(9)     // "123.456789000"
nr.toFixed(10)    // "123.4567890000"
nr.toFixed(11)    // "123.45678900000"

nr.toPrecision()      // "123.456789"
nr.toPrecision(0)     // błąd, parametr musi być z przedziału 1-100
nr.toPrecision(1)     // "1e+2"
nr.toPrecision(2)     // "1.2e+2"
nr.toPrecision(3)     // "123"
nr.toPrecision(4)     // "123.5"
nr.toPrecision(5)     // "123.46"
nr.toPrecision(6)     // "123.457"
nr.toPrecision(7)     // "123.4568"
nr.toPrecision(8)     // "123.45679"
nr.toPrecision(9)     // "123.456789"
nr.toPrecision(10)    // "123.4567890"
nr.toPrecision(11)    // "123.45678900"
```

W przypadku konwersji na wartość boolowską, możemy posłużyć się funkcją `Boolean()`:

```js
Boolean(102); //true
Boolean("kot"); //true
```

Podczas takiej konwersji wszystkie poniższe wartości staną się false:

```js
Boolean(false); //false
Boolean(null); //false
Boolean(undefined); //false
Boolean(0); //false
Boolean(NaN); //false
Boolean(""); //false
Boolean(document.all); //false
```

Więcej na ten temat dowiesz się w rozdziale o instrukcjach warunkowych.
