Falsy values i operatory porównania

Jest coś takiego w JavaScript, co się zowie jak powyżej. Niestety, bardzo często jest to temat niezrozumiały. Miałem okazję uczestniczyć bądź po prostu układać pytania do kilku rekrutacji na JavaScript developera w dużych firmach i muszę stwierdzić, że czasem bywa wręcz z falsy values dramatycznie.

O co takiego chodzi? Falsy values1 to nic innego, jak zbiór wartości, które dają fałsz. Coś w ten deseń:

if (!value) {
	console.log('Jedną z falsy values jest: '+ value);
}

Pytanie, jakie są to wartości? Oto one:

Czyli:

if (!NaN) {
	console.log('Jedną z falsy values jest: '+ NaN);
}
if (!null) {
	console.log('Jedną z falsy values jest: '+ null);
}

Wszystkie te wartości przy konwertowaniu na typ Boolean (np. !!NaN) dają false. Warto zapamiętać!

Operatory porównania w JavaScript

Jeśli mowa o falsy values ciekawe rzeczy dzieją się przy ich porównywaniu. Jak wiadomo, mamy operatory == i ===. Mnóstwo ludzi wykłada się na tym, banalnym zresztą (po głębszej analizie) problemie.

Warto zapamiętać, że pierwszy operator porównuje jedynie wartości, drugi – wartości oraz typy. Poniższe przykłady dają więc prawdę:

"0" == 0
"    " == 0
[] == 0
undefined == null
false == new String("")

Fałsz zwracają natomiast podobne konstrukcje:

NaN == 0
null == 0
undefined == ""
0 == {}

Kiedy idzie o operator ścisłego porównania robi się ciekawiej – JavaScript oprócz logicznej wartości porównuje również typy zmiennych (a więc czy są obiektem, stringiem, liczbą itd.). Prawdę zwracają:

0 === 0
"" === ""
[] !== [] // dla kontrastu..

Fałsz natomiast poniższe przykłady:

"" === 0 // typ string i number
0 === null // typ number i object
[] === 0 // typ object i number
[] === "    " // typ object i string

Warto więc być ostrożnym w trakcie pisania aplikacji w JavaScript.


  1. Temat falsy values poruszył na przykład Douglas Crockford w swojej znakomitej książce „JavaScript: The Good Parts”. Polecam!

Komentarze

1

Bardzo ciekawe. Wiele razy zastanawiałem się czy akurat „ta” konstrukcja zadziała… a nie było czasu na szukanie odpowiedzi w książkach czy necie. Tutaj mam skompilowaną odpowiedź.
Dzięki Ferrante!

Piotr
2

To tylko pokazuje, jak bardzo złym tworem jest JavaScript. I nie ma dla niego alternatywy. (Czy się mylę?)

3

JavaScript jest jaki jest i trzeba sie z tym pogodzic. Ja go lubie, ale z radoscia spogladam w strone zmian w kolejnych wersjach (napisze jakiegos posta o nich), ktore troche bardziej normalizuja programowanie. Aczkolwiek z checia bym uslyszal Twoja argumentacje Nowaker.

Pozdr.

4

A dlaczego właściwie [] === [] zwraca false?

5

13.Return true if x and y refer to the same object or if they refer to objects joined to each other (see 13.1.2). Otherwise, return false.

http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-262.pdf 57 strona

W ogole warto sie przyjrzec dokumentacji, bo powiedzenie ze === porownuje tylko typy jest przesadzone. Moze kiedys przetlumacze i wrzuce.

6

Ogólnie złem są zachowania, które nie są logiczne. Dużo z tego, co podałeś właśnie takimi jest. Już godzinę po przeczytaniu manuala idzie zapomnieć, że [] == 0 i {} != 0. Programista nie będzie wydajny, jeśli będzie musiał się zastanawiać, czy właściwie null jest zerem, czy pusta tablica jest zerem, czy też nie. Bo równie dobrze twórcy JS mogli założyć, że [] != 0, czyż nie? Nie ma logicznego argumentu na to, że pusta tablica powinna być równa zeru albo i nierówna. A jeśli nie ma, to programista będzie o tym zapominał.

No, napisz, co tam ma być w nowym JavaScripcie. W sumie front-end to nie moja działka, ale zawsze dobrze wiedzieć. Mógłbyś jeszcze mi tylko odpowiedzieć, czy istnieje w ogóle jakaś alternatywa dla JS.

7

Zgadzam się niestety z Nowakerem – te wyniki porównań nie są intuicyjne. Zmuszają (dopóki się nie ma tego w małym palcu) do skupiania się nad wydawałoby się pierdołami. Trochę PHPowo…

Jeszcze mam pytanie o:
# „” === new String() // dlaczego true skoro …
# [] !== [] // … false?

Czyżby JS też był językiem, w którym dwa Stringi o tej samej wartości są tym samym obiektem? Czy może to nie obiekt? :P

8

porownaj sobie:

alert(typeof new String());
alert(typeof "");

# [] !== [] - daje true

Zapraszam do specyfikacji ;-)

9

>>> typeof new String()
„object”
>>> typeof „”
„string”
>>> „” === new String()
false

Chyba że to firebug coś mi krzaczy.

10

no wiec jesli typy sie nie zgadzaja, to operator === zwraca false – przeczytaj dokumentacje. Wiem, dziwne.

11

Chciałem tylko Ci zwrócić uwagę, że masz w treści notki „” === new String() dające prawdę. Rozumiem, że to błąd.
I z tego co widzę głębsza analiza specyfikacji rzeczywiście jest potrzebna (ale jakoś nie mam na to ochoty w tym momencie). Pytanie tylko czemu nie mogłoby to być intuicyjne?

12

O matko, nie wiem, skad sie to tam wzielo, moje przeoczenie, jeszcze sam analizowalem ten przyklad przed pisaniem. Eh ;-)

Pozdrawiam

13

Nie jestem pewien, ale chyba znalazlem blad:

„”””
Czyli:

1. if (!NaN) {
2. console.log(‚Jedną z falsy values jest: ‚+ value);
3. }

1. if (!null) {
2. console.log(‚Jedną z falsy values jest: ‚+ null);
3. }

Wszystkie te wartości dają false. Warto zapamiętać!
„”””

Zamiast:
if (!NaN)
powinno byc chyba:
if (NaN)

tak samo z if (!null).

cx
14

Nie masz racji ;)

15

Odnosząc się do szóstego komentarza autorstwa Nowakera.

Zgadzam się, że działanie operatorów porównań nie jest łatwe do odgadnięcia „na logikę” na samym początku, ale po przeczytaniu specyfikacji wszystko staje się jasne.
Wystarczy w zasadzie zapamiętać, że jeśli dokładnie jeden z operandów jest obiektem to przy operatorze == zostanie sprowadzony do wartości prymitywnej.
Dlatego [] == 0, [] == „”, [0] == 0, nawet „[object Object]” == {} zwrócą true.
Dopiero wartości prymitywne są ze sobą porównane.
Gdy obydwa operandy są obiektami, to porównywane są referencje.

Jeśli porównujemy String z liczbą / liczbę ze Stringiem, zawsze String zostanie sprowadzony do wartości liczbowej. W ten sposób prawdę zwrócą „1e1” == 10, 10 == „1e1”

W przypadku, gdy jednym z operandów jest wartość logiczna (true/false), operand ten będzie sprowadzony do liczby i dopiero porównany z drugim, dlatego prawdą będzie:
true == 1
true == „1”
false == 0
false == „0”
false == [0]

mam nadzieję, że nic nie pokręciłem ;-)

16

@Rafał, chodzi właśnie o to, by nie czytać żadnej specyfikacji. Aczkolwiek niżej podałeś ludzkie reguły rządzące tymi porównaniami.

17

Dlaczego nie mam racji? Przeciez
!null == true
a nie false, a jest napisane „Wszystkie te wartości dają false. „

cx
18

Poprawione w tekscie.

19

a po co zawracać sobie głowę ==, != ?

20

Marek podał idealne rozwiązanie problemu i jedyny sposób, by nie zaprzątać sobie tym wszystkim głowy – wystarczy wyrzucić z głowy te dwa operatory ‚==’ oraz ‚!=’ i używać ich bardziej precyzyjnych odpowiedników, czyli ‚===’ oraz ‚!==’ – robię tak od ponad roku (od kiedy na porządku dziennym używam JS Lint) i w żaden sposób mi ich nie brakuje.

Kamil T.
21

if ( 0 == false ) document.write( ‚0 == false’ ); else document.write( ‚0 != false’ );
document.write(”);
if ( null == false ) document.write( ‚null == false’ ); else document.write( ‚false != null’ );
document.write(”);
if ( 0 == null ) document.write( ‚0 == null’ ); else document.write( ‚0 != null’ );
document.write(”);

if ( null )
{
document.write( ‚null is true’ );
}
else
{
document.write( ‚null is not true’ );
}

document.write(”);
if ( null == true )
{
document.write( ‚null is true’ );
}
else
{
document.write( ‚null is not true’ );
}

document.write(”);
if ( null == false )
{
document.write( ‚null is false’ );
}
else
{
document.write( ‚null is not false’ );
}

0 == false
false != null
0 != null
null is not true
null is not true
null is not false

Strasznie nielogiczne. Po to jest === zeby dostac ( null === false ) != true…

pakalk
22

[…] do analizy tej linii mamy prosty warunek. Jako że NaN to jedna z falsy values i przy skonwertowaniu do wartości logicznej da false, JS spróbuje wziąć sobie zmienną foobar i […]

23

Wystarczy w zasadzie zapamiętać, że jeśli dokładnie jeden z operandów jest obiektem to przy operatorze == zostanie sprowadzony do wartości prymitywnej.
Dlatego [] == 0, [] == ""

Do jakiej wartości prymitywnej jest zatem sprowadzana pusta tablica? Czy można w kodzie sprawdzić do jakiej wartości prymitywnej jest sprowadzany dowolny obiekt?

Piotr Dobrogost
24

[…] wywołamy ten konstruktor bez parametrów (patrz [6]) to nie wejdzie do tego ifa. Czytaj więcej o falsy values. 3. Jeśli podaliśmy jakiś ciąg jako parametr (patrz [7]) to do właściwości `sentence’ […]

25

Sądzę, że Javascript nie jest tak złym językiem, jak niektórzy ją malują :)

Jest jaka jest [i jak to z kobietami] należy po prostu wiedzieć, że czasem dziwnie się zachowuje, może nieracjonalnie, ale wiedząc, to można całkiem zgodnie żyć [czasem :D].

Jednak chciałbym zauważyć, że tak naprawdę to JS staje się powoli „asemblerem przeglądarek”. Mało kto pisze już w natywnym JS. Nawet jeśli – to często tworząc sobie obiekty, które już mają bardziej racjonalne działanie.

Przykładami zasemblerzenia się JS jest choćby GWT – Java jest _kompilowana_ do JS.

26

[…] ferrante.pl/2009/09/05/falsy-values-i-operatory-porownania – po polsku […]

27

[…] swój klimat. Design oparty jest na rysunkach postaci, nazwa konferencji nawiązuje do słynnych wartości fałszywych w JS. Spójrzcie na nazwy pakietów sponsorskich: Global, Context oraz Closure. Mamy ambicję zostać […]

28

[…] swój klimat. Design oparty jest na rysunkach postaci, nazwa konferencji nawiązuje do słynnych wartości fałszywych w JS. Spójrzcie na nazwy pakietów sponsorskich: Global, Context oraz Closure. Mamy ambicję zostać […]

29

[…] barykady". Tantek zauważył, że różne języki w różny sposób implementują porównywanie tzw. "falsy values" i odkrył, że można tym sterować w taki sposób, żeby na podstawie zbioru testów logicznych […]

30

[…] Falsy values i operatory porównania. Kompendium na blogu Ferrante dotyczący falsy values – wartości, które zwracają […]

Dodaj komentarz

Dozwolone tagi: <blockquote>, <code>, <strong>