Konwersja jawna i niejawna #10

Konwersja typów

W JavaScript będziemy mieć bardzo często do czynienia z konwersją typów. Z konwersją będziemy spotykać się przy porównywaniu wartości, wykonywaniu działań matematycznych, konkatenacji ciągów znakowych czy nawet przy serializacji do formatu JSON.

Wielokrotnie sami będziemy decydowali o konwersji danych, będzie to wtedy konwersja jawna. Znacznie częściej jednak będzie odbywała się konwersja niejawna. Konwersja niejawna rządzi się wieloma regułami i zasadami. Zrozumienie ich może nam ułatwić pracę z JavaScript.

Nie ważne, która z nich występuje, każda konwersja w JavaScript to konwersja tylko do typu prymitywnego. W wyniku konwersji otrzymamy zawsze wartość typu boolean, string albo number.

// boolean, string, number

Dodatkowo konwersja dla typów prymitywnych i obiektowych różnią się. W tym dziale zajmiemy się typami prymitywnymi. Gdy będziemy omawiać obiekty, wrócimy jeszcze do konwersji i tego jak one zachowują się gdy musi nastąpić ich konwersja do typu prymitywnego.

Konwersja jawna

Jeśli chodzi o konwersję jawną to dzieje się wtedy, kiedy w kodzie zastosujemy konkretną instrukcję. Dobrym przykładem jest wykorzystanie funkcji Boolean(), String() i Number().

console.log(Boolean('false')); // true
console.log(String(42)); // '42'
console.log(Number('42.12')); // 42.12

Ten zapis mówi nam dokładnie o tym, że wartości zostaną przekonwertowane na odpowiednie typy.

Jeżeli podamy do tych funkcji poprawne formy danych, otrzymamy spodziewany wynik. Każdą z tych funkcji omówimy jeszcze dokładnie w dalszej części działu.

Konwersja niejawna

W JavaScript częściej będziemy mieli jednak do czynienia z niejawną konwersją, która często zaskakuje programistów swoimi wynikami. Niejawna konwersja bardzo często stosowana jest przy wszelkich porównaniach, instrukcjach warunkowych. Często wynikiem niejawnej konwersji jest typ boolean.

Jeden z popularniejszych przypadków w kodzie to sprawdzenie, czy zmienna ma jakąś wartość:

const a = 'false';
if (a) {
    console.log(Boolean(a))
}

W tym przypadku zmienna a reprezentuje typ string. Ten łańcuch znaków nie jest pusty, a niepusty string
konwertowany jest na wartość true. Ostatecznie więc zmienna a na potrzeby instrukcji warunkowej if zostaje niejawnie skonwertowana do typu boolean o wartości true.

Zobaczmy kolejny przypadek:

const b = ' ';
if (b) {
  console.log(Boolean(b)) // true
}

Tutaj również sprawdzamy, czy zmienna b ma jakąś wartość. Na pierwszy rzut oka wydaje nam się, że nie. Jednak spacja to także wartość w JavaScript i choć mogłoby się wydawać, że nic tam nie ma, nastąpi niejawna konwersja do typu boolean i będzie ona miała wartość true.

Kolejnym przypadkiem niejawnej konwersji jest dodanie pustego stringa:

const c = 42 + "";
console.log(c) // '42'

Tak naprawdę jest to operacja dodawania. Jednak gdy jedna ze stron reprezentuje typ string następuje konwersja drugiej strony do typu string. Dlatego też ostatecznie otrzymujemy liczbę zapisaną jako string.

Operator == i problemy

Szczególnie dużo niejawnych konwersji zachodzi przy operatorach porównania, arytmetycznych i logicznych. Cały system konwersji w JavaScript jest bardzo rozbudowany i ma swoje konkretne zasady.

Szczególny problem powoduje operator podwójnego porównania == . Zapamiętanie wszystkich możliwych wyników, jakie daje ten operator, nie jest możliwe.

Dlatego tak często operator ten zaskakuje programistów, a używanie go dzisiaj to proszenie się o problemy. Jednak nadal operator ten istnieje w JavaScript, jest też częstym elementem dyskusji na rozmowie o pracę i warto o nim wiedzieć coś więcej.

Rozważmy taki przypadek, z którego otrzymujemy wartość false:

console.log(true == 'true'); // false

Porównujemy wartość true z napisem 'true'. Moglibyśmy wydedukować, że jeżeli po lewej jest wartość boolean to wartość po prawej stronie będzie przekonwertowana do wartości true w typie boolean.

Niestety, przy porównaniach, działają zupełnie inne zasady i możemy je przejrzeć w specyfikacji ECMAScript https://www.ecma-international.org/ecma-262/5.1/#sec-11.9

Gdy przeanalizujemy nasze porównanie zgodnie z tym, co jest w specyfikacji, zobaczymy zapis, że:

If Type(x) is Boolean, return the result of the comparison ToNumber(x) == y.

Jeżeli pierwszą wartością jest boolean to następuje niejawna konwersja boolean na number.

Po pierwszej niejawnej konwersji porównanie zmienia się na taką postać:

Number(true) // 1
console.log(1 == 'true') // false

w specyfikacji czytamy dalej:

If Type(x) is Number and Type(y) is String,
return the result of the comparison x == ToNumber(y).

Jeżeli teraz pierwszą wartością jest number a drugą wartością string to należy przekonwertować drugą wartość na number. Nasze porównanie więc znowu zmienia postać taką:

Number('true'); // NaN
console.log(1 == NaN)

Wszystko zostało sprowadzone do typu number i na końcu sprawdzane jest, czy liczba jeden równa się NaN. Pamiętajmy, że w JavaScript NaN to number, są to więc dwie różne liczby. Otrzymujemy więc wartość false, pomimo, że mogliśmy się spodziewać wartości true. Przeszliśmy przez szereg
niejawnych konwersji, a wynik dla nas nie był oczywisty.

Spamiętanie tego jest bardzo trudne. W Internecie znajdziecie świetną stronę, autorstwa użytkownika Dorey która pokazuje przypadki konwersji przy porównaniu, a także konwersję przy instrukcji if https://dorey.github.io/JavaScript-Equality-Table/

Tabela prezentujące możliwe wyniki przy operatorze podwójnego porównania jest trudna do zapamiętania i nawet nie należy próbować jej zapamiętać.

Wniosek z tych wszystkich materiałów jest zawsze taki, że najlepiej używać potrójnego operatora porównania. Który nie dokonuje konwersji. Prezentuje to już druga tabelka:

Zadaniem potrójnego operatora jest sprawdzenie, czy wartości są takie same i mają ten sam typ. Nie zachodzą żadne niejawne konwersje, a tabelka w odczytaniu jest już bardzo prosta. Otrzymujemy true tylko tam, gdzie w porównaniu mamy dokładnie ten sam typ i tę samą wartość.

Wróćmy jeszcze na chwilę do naszego przykładu.

Może nam bardzo zależeć na porównaniu takich wartościach jak boolean i string, Jeżeli chcemy to koniecznie zrobić najlepiej zastosować jawną konwersję do typu boolean:

console.log(true === Boolean('true')); // true

Dokonując konwersji jawnej i stosując potrójny operatora porównania, kod mamy pod większą kontrolą i nie jesteśmy zdani na wynik niejawnej konwersji, który jest trudny do przewidzenia. Tym sposobem unikniemy wielu problemów.

Porównanie null za pomocą ==

Czasami możecie jednak zobaczyć w użyciu podwójne porównanie. Jedynym przypadkiem, w którym podwójny operator sprawdza się to porównanie do wartości null:

const x = undefined;
const y = null;
const z = 'test';
console.log(x == null) // true
console.log(y == null) // true
console.log(z == null) // false

Ten zwięzły zapis pozwala sprawdzić, czy wartość nie jest null lub undefined.

Nie wiem jednak, czy robienie w kodzie wyjątku na takie podwójne porównanie przyniesie Wam wiele korzyści. Osobiście z podwójnego operatora porównania zrezygnowałbym całkowicie. Choćby dla ujednolicenia zasad w kodzie.

Dalszymi przypadkami konwersji zajmiemy się w kolejnych filmach, gdzie poznamy operatory porównania, operatory logicznego porównania i różne sposoby jawnej i niejawnej konwersji.

Co warto zapamiętać:

  • w JavaScript mamy konwersję jawną i niejawną
  • każda konwersja to sprowadzanie do wartości prymitywnej
  • operatory często stosują niejawną konwersję, operator podwójnego porównania jest szczególnie trudny do rozszyfrowania
  • w Internecie mamy dostęp do tabel, które pokazują konkretne przypadki porównania
  • jeżeli używamy operatora porównania to tylko potrójnego