Gorący temat. Najbardziej kulturalne dyskusje kończy rękoczynami i rzucaniem internetowych klątw na odważnych entuzjastów podwójnej negacji. A ja cenię sobie spokojne życie, więc na wstępie chciałabym zaznaczyć fundamentalny cel tego artykułu. Tak więc, drogi Czytelniku, dziś dzielę się z Tobą wiedzą, a nie rekomendacją.
Teraz, ze spokojnym sercem, mogę przejść do rzeczy.
Czym jest podwójna negacja?
Inaczej double negation, double bang, bang bang, NOT NOT, !!
.
Przede wszystkim !!
nie jest operatorem logicznym.
!!
jest podwójnym zastosowaniem operatora logicznego NOT, czyli !
.
Podwójną negację wykorzystuje się – ogólnie mówiąc – w celu konwersji na typ prosty boolean
(true
lub false
).
Dygresja: Operator logiczny NOT
Przypomnijmy sobie, jak działa operator logiczny NOT. Zwraca on false
wtedy, kiedy jego argument konwertuje się do true
. W innym przypadku zwraca true
.
Oto JEDYNE* wartości, które w świecie JS są falsy, czyli konwertują się do false
lub są równoznaczne false
.
false
null
;NaN
;0
;- pusty string (
""
/''
/``
); undefined
.
Sposób działania podwójnej negacji
Z powyższą wiedzą dotyczącą !
sposób działania !!
jest oczywisty. Pierwsza negacja to konwersja jakiejkolwiek wartości na true
albo false
, a następnie odwrócenie jej. Druga negacja otrzymanej wartości boolean
sprawia, że otrzymujemy rzeczywistą reprezentację początkowej wartości w świecie wartości typu boolean
. I wszystko jasne, prawda?
!!x
zwróci false
, kiedy wartość x
to null
/NaN
/undefined
/0
/""
/false
!!x
zwróci true
, kiedy wartość x
to jakakolwiek inna, niewymieniona powyżej wartość.
Kilka przykładów:
!!false === false
!!true === true
!!0 === false
!!1 === true
!!-1 === true
!!parseInt("foo") === false // NaN is falsy
!!"" === false // empty string is falsy
!!"foo" === true // non-empty string is truthy
!!"false" === true // ...even if it contains a falsy value
!!undefined === false
!!null === false
!!{} === true // an (empty) object is truthy
!![] === true // an (empty) array is truthy
Przykładowe zastosowanie
Podwójną negację można wykorzystać, aby mieć pewność, że zwracana wartość będzie typu boolean
. Jest to przydatne podczas zastosowania owych wartości jako argumenty w konstrukcjach warunkowych.
REACT CASE
Przejdę teraz do sedna. Pierwszy raz spotkałam się z podwójną negacją właśnie w projekcie opartym na bibliotece React. Spójrzmy na poniższy przykład, w którym chcemy powiadomić użytkownika o pojawieniu się nowych, nieprzeczytanych wiadomości. Z życia wzięte.
{unreadMessages.length && <div>You have {unreadMessages.length} unread messages!</div>}
Dygresja: Operator logiczny AND
Przypomnijmy sobie, jak działa operator logiczny &&
.
Ten operator jest wykorzystywany z co najmniej 2 argumentami. Zwraca wartość jednego ze swoich argumentów, więc jeśli któryś z tych operatorów jest użyty z wartością nie-logiczną, to zwróci wartość nie-logiczną. Zwracaną wartością będzie ostatni argument dla przypadku, kiedy wszystkie argumenty konwertują się do true
lub pierwszy argument, który konwertuje się do false
.
W skrócie:
expr1 && expr2
zwróć expr1
, jeśli jest konwertowany do false
; w innym wypadku, zwróć expr2
.
Wróćmy teraz do naszego React’owego przykładu wyświetlania powiadomienia o nieprzeczytanych wiadomościach. Powiedz mi, co wyświetli się dla przypadku, kiedy ich nie będzie (unreadMessages=[]
) ?
{unreadMessages.length && <div>blabla</div>}
Oczywiście nasza aplikacja odważnie wyświetli 0
. I będzie miała rację.
unreadMessages.length
zwróci 0
, które – zgodnie z tym, co ustaliliśmy wcześniej – konwertuje się do false
.
Wobec tego nasze wyrażenie zwróci pierwszą falsy wartość, czyli nieszczęsne 0
.
Jak to naprawić?
- Naszym bang bang operatorem:
!!unreadMessages.length
, czyli!!0
konwertuje się dofalse
. Ostatecznie aplikacja nic nie wyrenderuje.
- Dokumentacja jednak sugeruje poniższe, klasyczne rozwiązanie:
{unreadMessages.length > 0 && <div>...</div>}
- Wśród nas znajdą się pewnie zwolennicy i takiej metody…
{unreadMessages.length ? <div>...</div> : false}
Zainteresowanych słusznością użycia tego operatora warunkowego odsyłam do mojego innego artukułu.
Zawsze jest jakieś ALE…
Podwójna negacja nie cieszy się dobrą reputacją w środowisku programistów. Zdecydowana większość krzyczy, iż taka składnia jest nieczytelna.
Jak dla mnie – niekoniecznie.
Nie będę wkładać kija w mrowisko (nie dziś), więc nie zdradzę Ci moich rekomendacji. W zamian za to zapoznam Ciebie z alternatywami podwójnej negacji.
1. Boolean()
Podwójną negację !!(expr)
można zastąpić jawną konwersją do typu boolean
, czyli: Boolean(expr)
.
Boolean(3) === !!3;
Pamiętaj jednak o poprawnej składni!
!!new Boolean(false) // true
!!Boolean(false) // false
Wywołanie new Boolean(false)
tworzy nowy obiekt. A każdy obiekt w JS konwertuje się do true
(nawet jeśli zawiera w sobie falsy wartość).
Dopiero wywołanie funkcji Boolean(false)
zadziała w oczekiwany przez nas sposób, zwracając wartość prymitywną.
2. Optional chaining operator
Opisywałam go już na blogu o tutaj. Artykuł jest nieco zamierzchły, z czasów, kiedy operator był jeszcze w Stage 1. Mimo wszystko sposób jego działania nie zmienił się od tamtej pory.
Optional chaining operator z pewnością przyda się podczas sprawdzania istnienia własności obiektu.
W skrócie:
obj?.prop
– jeśli obj
jest równe null
lub undefined
– całe wyrażenie jest konwertowane do undefined
3. .length
Opisane w powyższym przykładzie – stare, dobre sprawdzanie długości tablicy.
4. Wiara…
…w automatyczną, niejawną koercję (implicit coercion). JS sam próbuje dokonywać konwersji, kiedy spotyka się z niespodziewanym typem.
Na przykład:
if (-1) // true
if ("0") // true
if ({}) // true
Jednak ślepa wiara w koercję JSa nie jest najlepszą praktyką wśród programistów. JS z pewnością ma dobre intencje, jednak efekty jego działania mogą być dalekie od naszych oczekiwań.
A teraz ustępuję miejsca osobom, które chcą wziąć udział w dyskusji na temat (nie)słuszności podwójnej negacji. Korzystacie z niej? Dlaczego tak, dlaczego nie? Jakie znacie alternatywy? Piszcie w komentarzach, jestem ciekawa Waszych odpowiedzi!
Przypisy:
* >> patrz komentarze
Źródła:
KLIK, KLIK + wszystkie odnośniki, które pojawiły się bezpośrednio w tekście.