Operatory logiczne w JavaScript (AND, OR, NOT) można łączyć w jednym wyrażeniu — AND ma wyższy priorytet niż OR, a NOT wyższy niż oba, dlatego dla czytelności warto grupować warunki nawiasami. Najczęstsze błędy to traktowanie pustego obiektu {} lub tablicy [] jako wartości fałszywej (są to wartości truthy), pomijanie kolejności interpretacji wyrażenia od lewej strony oraz nadawanie zmiennym mylących nazw z przeczeniem (np. isNotLoaded), które komplikują odczyt kodu z operatorem NOT. Długie wyrażenia logiczne najlepiej przenosić do osobnej, nazwanej funkcji — to znacznie ułatwia debugowanie i czytelność kodu.
Jest to kontynuacja tematu operatorów logicznych w JavaScripcie. Jeśli nie znasz jeszcze podstaw, zapraszam Cię do lektury poprzedniego artykułu: Operatory logiczne w JavaScript – podstawy. Tutaj skupimy się na praktyce: jak operatory wyglądają w prawdziwym kodzie, jak je łączyć i dlaczego czasem zachowują się inaczej, niż się spodziewasz.
Jak stosować zmienne w wyrażeniach logicznych?
W praktyce nie wpisuje się wartości do wyrażeń bezpośrednio (np. "nice day" && "cheerful people"). Przechowuje się je w zmiennych (czasem też zwraca bezpośrednio z funkcji, ale o tym za chwilę). Zasada działania operatorów logicznych się nie zmienia – w miejsce zmiennych interpreter JavaScript podstawi sobie przechowywane przez nie wartości. Kilka przykładów wystarczy dla zrozumienia tematu:
// operator AND
const user = "regular"
const isLoggedIn = true
console.log(user === "regular" && isLoggedIn) // true, ponieważ operator porównania zwraca true i zmienna isLoggedIn przechowuje true
// operator OR
let isGameEnd = false
let score = 0
console.log(isGameEnd || score > 0) // false, ponieważ zmienna isGameEnd przechowuje false i operator porównania zwraca false
// operator NOT
const student = {name: "Lizzy", age: 21}
console.log(!student) // false -> zanegowaliśmy wartość prawdziwą (obiekt to wartość truthy), więc otrzymujemy false
// dla utrwalenia jeszcze podwójna negacja:
const animal = "cat"
console.log(!!animal) // true
Jak użyć wartości zwracanej z funkcji w wyrażeniu logicznym?
W większości przypadków wartości będą trafiać do naszych zmiennych dynamicznie – czyli z funkcji, których wynik działania zależy od jakichś zewnętrznych czynników, np. od tego:
- jakie dane użytkownik wprowadził do formularza,
- jaką akcję użytkownik wykonał (np. czy coś kliknął),
- jaki rodzaj użytkownika się zalogował,
- czy udało nam się pobrać dane z serwera.
Te wszystkie operacje obsługiwane są przez funkcje. Dla przykładu stwórzmy sobie takie uproszczone funkcje, które weryfikują wartości od użytkownika. Sprawdzimy, czy użytkownik znajdzie największą liczbę oraz literę, która występuje w alfabecie wcześniej od innych:
// wyświetlam użytkownikowi okienko z inputem i wprowadzoną przez niego wartość zapisuję od razu w zmiennej:
const userNumber = prompt('Która liczba jest największa? -> 3, 10, 5');
// to samo robię z drugim zadaniem
const userLetter = prompt(
'Która litera alfabetu występuje wcześniej niż inne? -> r, k, f'
);
function isBiggest(number) {
return number === '10'; // wartość z okienka prompt zawsze jest stringiem, więc porównam ją do stringa
}
function isCorrectLetter(letter) {
return letter === 'f';
}
// zapisuję wynik działania funkcji do zmiennych
const isNumberGuessed = isBiggest(userNumber);
const isLetterGuessed = isCorrectLetter(userLetter);
// aby użytkownik zwyciężył, obie wartości zwracane z funkcji muszą być true – wówczas zmienna isWinner będzie przechowywać true
// jeśli zmienna isWinner będzie przechowywać false, użytkownik przegra
const isWinner = isNumberGuessed && isLetterGuessed;
console.log(isWinner);
Uwaga: zauważ, że każda z funkcji zawiera słówko return. Jest ono niezbędne, aby funkcja zwróciła wartość (i abyśmy mogli z niej skorzystać).
W powyższym przykładzie w wyrażeniu logicznym umieściliśmy zmienne przechowujące wynik działania funkcji. Interpreter JS wstawił sobie w to miejsce wartości zwracane z funkcji:
// jeśli użytkownik wprowadzi poprawne wartości, interpreter JS odczyta to tak:
const isWinner = true && true // co da w efekcie true
// jeśli użytkownik wprowadzi np. niepoprawną liczbę, interpreter JS odczyta to tak:
const isWinner = false && true // co da w efekcie false
Kod sprawdzający zwycięstwo zadziałałby dokładnie tak samo, gdybyśmy do wyrażenia logicznego wstawili wywołanie funkcji:
const isWinner = isBiggest(userNumber) && isCorrectLetter(userLetter)
console.log(isWinner)
Czasem jednak warto zadbać o czytelność kodu i wybrać rozwiązanie zastosowane przez nas wcześniej. Jeżeli temat funkcji jest Ci jeszcze nieznany, polecam artykuł dla początkujących: Deklaracja i wywołanie funkcji w JavaScript.
Jak łączyć operatory logiczne i w jakiej kolejności są wykonywane?
Operatory logiczne można łączyć! Należy brać pod uwagę, że:
- takie wyrażenie nadal jest interpretowane od lewej,
- operator AND ma wyższy priorytet niż OR, a NOT ma wyższy priorytet niż oba poprzednie,
- dla przejrzystości zapisu warto stosować nawiasy – one mają najwyższy priorytet.
Pełna kolejność, od najwyższego priorytetu (wykonywane najpierw) do najniższego:
| Priorytet | Operatory | Przykład |
|---|---|---|
| 1 (najwyższy) | nawiasy ( ) | (a || b) && c |
| 2 | negacja ! | !isGameEnd |
| 3 | porównania < > <= >= | score >= 10 |
| 4 | równość === !== == != | type === "admin" |
| 5 | AND && | a && b |
| 6 | OR || i nullish ?? | a || b |
| 7 (najniższy) | operator warunkowy ? : i przypisania = | a ? b : c |
Przykładowo poniższe wyrażenie zwróci wartość truthy:
console.log("nice day" && "coffee" || null) // "coffee"
Odczytywane jest bowiem po kolei: "nice day" && "coffee" daje wartość truthy (zwracany jest string "coffee") i gdy dochodzimy do operatora ||, kończymy sprawdzanie. Pamiętasz zasadę dla OR? „Po co sprawdzać resztę, skoro już wystąpiła wartość prawdziwa". Skomplikujmy:
console.log("coffee" && null || 2 && 3) // 3
Pamiętajmy: AND ma wyższy priorytet niż OR, więc interpreter nasze wyrażenie pogrupuje (sami też możemy to zrobić w kodzie):
("coffee" && null) || (2 && 3)
Pierwsza operacja zwróci null, a więc wartość fałszywą. Druga operacja zwróci 3, a więc wartość prawdziwą. Ostateczna operacja to:
null || 3 // 3
Został jeszcze operator NOT. Wplećmy go w poprzedni przykład:
console.log("coffee" && !null || 2 && 3) // true
Sprawdzamy od lewej: "coffee" to wartość prawdziwa, !null zwraca true, więc to też wartość prawdziwa. Ostateczny wynik dla "coffee" && !null to właśnie true (pamiętajmy, że operator AND zwraca ostatnią wartość, jeśli wszystkie były prawdziwe). I na tym kończymy, ponieważ napotykamy operator ||, a w jego przypadku „po co sprawdzać resztę, skoro już wystąpiła wartość prawdziwa".
Jak korzystać z operatorów logicznych w JS – przykłady
Jak użyć operatorów logicznych w instrukcji warunkowej?
Instrukcja warunkowa to chyba najczęstsze miejsce wykorzystania operatorów logicznych. Weźmy nasz przykład ze zgadywaniem liczby i litery. Moglibyśmy wyświetlać komunikat o wygranej lub przegranej:
const userNumber = prompt('Która liczba jest największa? -> 3, 10, 5');
const userLetter = prompt(
'Która litera alfabetu występuje wcześniej niż inne? -> r, k, f'
);
function isBiggest(number) {
return number === '10'; // wartość z okienka prompt zawsze jest stringiem, więc porównam ją do stringa
}
function isCorrectLetter(letter) {
return letter === 'f';
}
const isNumberGuessed = isBiggest(userNumber);
const isLetterGuessed = isCorrectLetter(userLetter);
const isWinner = isNumberGuessed && isLetterGuessed;
// instrukcja warunkowa korzystająca z wyrażenia logicznego zamieszczonego w zmiennej isWinner
if (isWinner) {
alert('Gratulacje, wygrałeś!');
} else {
alert('Odpowiedzi nieprawidłowe. Spróbuj ponownie');
}
Jeżeli wyrażenie logiczne jest krótkie jak w tym przypadku, możemy pominąć etap tworzenia zmiennej isWinner i od razu wpisać wyrażenie do warunku:
const isNumberGuessed = isBiggest(userNumber);
const isLetterGuessed = isCorrectLetter(userLetter);
// instrukcja warunkowa korzystająca bezpośrednio z wyrażenia logicznego
if (isNumberGuessed && isLetterGuessed) {
alert('Gratulacje, wygrałeś!');
} else {
alert('Odpowiedzi nieprawidłowe. Spróbuj ponownie');
}
Jak przenieść warunek do funkcji?
Pamiętaj, że jeśli wyrażenie logiczne jest długie i łatwo się w nim zgubić, to warto przenieść je do funkcji i to ją wstawić w miejsce wyrażenia. Na początku nauki, zanim poznasz sposoby optymalizacji różnych rozwiązań (np. obsługi błędów w formularzu JS), będziesz pewnie tworzyć takie konstrukcje:
if (
nameValue.length > 0 &&
surnameValue.length > 0 &&
emailValue.includes('@') &&
passwordValue.length >= 6
) {
console.log('The registration was successful');
} else {
console.log('Try again');
}
Wówczas przy refaktoryzacji warto przenieść warunek do funkcji, np. tak:
// warunek czytamy: jeżeli formularz jest wypełniony prawidłowo, to…
if (isFormFilledCorrectly()) {
console.log('The registration was successful');
// w przeciwnym razie…
} else {
console.log('Try again');
}
// wyrażenie logiczne przeniesione do funkcji
function isFormFilledCorrectly() {
return (
nameValue.length > 0 &&
surnameValue.length > 0 &&
emailValue.includes('@') &&
passwordValue.length >= 6
);
}
Zwróć ponownie uwagę, że należy pamiętać o słowie return – w przeciwnym razie ciągle będziemy otrzymywać wartość falsy (undefined). Jeżeli chcielibyśmy wykonać jakąś akcję zależną tylko od tego, że pola nie są wypełnione prawidłowo, możemy użyć operatora NOT:
// warunek czytamy: jeżeli formularz NIE jest wypełniony prawidłowo, to…
if (!isFormFilledCorrectly()) {
alert('The form contains invalid values and cannot be submitted.');
}
Z code review w devmentor.pl: gdy przenosimy długi warunek do osobnej funkcji, najczęstszy błąd początkujących to zapomniane return. Funkcja działa „po cichu", zwraca undefined, a warunek jest zawsze fałszywy. Pierwszy krok debugowania: sprawdź w konsoli, co zwraca sama funkcja, zanim zaczniesz podejrzewać operatory.
Jak dynamicznie przypisać wartość do zmiennej za pomocą && i ||?
To, CO zwraca operator (a nie JAKIE wartości: truthy czy falsy) jest nieraz wykorzystywane do dynamicznego ustawiania wartości zmiennych. Oznacza to, że nie musimy tworzyć instrukcji warunkowej, która nam te wartości ustawi. Stanie się to „samo" dzięki logice działania operatorów.
Operator AND – zabezpieczenie przed błędem:
const user = logUser() // spodziewamy się otrzymania z funkcji obiektu z danymi użytkownika
const userName = user && user.name
W przykładzie zmienna user będzie przechowywać obiekt z danymi użytkownika tylko wówczas, gdy logowanie się powiedzie, w przeciwnym razie zmienna będzie mieć wartość falsy (np. funkcja zwraca null). Dzięki temu odczytamy nazwę użytkownika tylko wówczas, gdy będziemy mieć już dostęp do obiektu. Jeśli zmienna user będzie mieć wartość falsy, to dalsza część kodu się nie wykona – w ten sposób zabezpieczamy się przed błędem. Gdybyśmy tego nie zrobili, otrzymalibyśmy komunikat, że próbujemy odczytać właściwość .name na obiekcie, którego nie ma: Uncaught TypeError: Cannot read properties of null (reading 'name').
Dziś to samo zrobisz krócej za pomocą operatora ?. (optional chaining):
const userName = user?.name // undefined, jeśli user to null lub undefined
Operator OR – wartość zapasowa:
Wyobraźmy sobie, że mamy projekt, w którym style zgromadziliśmy w osobnym pliku jako motyw. W przykładzie wybrany kolor umieściliśmy wcześniej w zmiennej themeColor i teraz chcemy ustawić go dla tekstu elementu articleElement.
articleElement.style.color = themeColor || "black"
Jeśli nie będziemy mieć danych o kolorze z motywu w zmiennej themeColor (np. zabraknie odpowiedniego pliku i zmienna będzie przechowywać wartość falsy), to ustawi się kolor czarny. Służy to jako backup.
Uważaj tylko, gdy 0, false albo pusty string są dla Ciebie poprawnymi wartościami – wtedy || podmieni je na wartość zapasową. W takim przypadku sięgnij po ??, który reaguje wyłącznie na null i undefined. Różnicę z przykładami opisaliśmy w sekcji Czym różni się ?? od || w JavaScript.
Czym są operatory przypisania &&=, ||= i ??=?
Operatory logiczne można połączyć z przypisaniem. Zamiast pisać x = x || "wartość", możesz zapisać krócej x ||= "wartość". Wszystkie trzy działają na tej samej zasadzie skracania co zwykłe &&, || i ?? – przypisanie następuje tylko wtedy, gdy warunek jest spełniony:
| Zapis | Znaczy to samo co | Przypisuje, gdy… |
|---|---|---|
a ||= b | a = a || b | a jest falsy |
a &&= b | a = a && b | a jest truthy |
a ??= b | a = a ?? b | a to null lub undefined |
let nickname = ""
nickname ||= "Gość"
console.log(nickname) // "Gość" -> pusty string jest falsy, więc został podmieniony
let quantity = 0
quantity ??= 10
console.log(quantity) // 0 -> ?? podmienia tylko null i undefined
const profile = { name: "ania" }
profile.name &&= profile.name.toUpperCase()
console.log(profile.name) // "ANIA" -> przypisanie wykonało się, bo name było truthy
Przypisanie, które się nie wykona, nie zostanie nawet „dotknięte". Jeśli profile.name byłoby puste, toUpperCase() w ogóle by się nie uruchomiło.
Kiedy użyć operatora warunkowego ? : zamiast && i ||?
Gdy wybierasz jedną z dwóch wartości zależnie od warunku, czytelniejszy będzie operator warunkowy (ternary) warunek ? gdyPrawda : gdyFałsz:
const buttonLabel = isLoggedIn ? "Wyloguj" : "Zaloguj"
Można to zapisać też przez && i ||, ale to pułapka:
const label = isLoggedIn && "Wyloguj" || "Zaloguj"
Zadziała tylko dopóki środkowa wartość jest truthy. Jeśli zamiast "Wyloguj" pojawi się 0 albo "", wyrażenie przejdzie do "Zaloguj" mimo spełnionego warunku. Operator ? : takiego problemu nie ma.
Uważaj na priorytety: operator ? : ma jeden z najniższych, więc dodawanie tekstu przed nim zmienia wynik:
const isLoggedIn = false
const message = "Witaj, " + isLoggedIn ? "Ania" : "Gość"
console.log(message) // "Ania" -> najpierw wykonało się "Witaj, " + isLoggedIn, a to niepusty string (truthy)
const fixedMessage = "Witaj, " + (isLoggedIn ? "Ania" : "Gość")
console.log(fixedMessage) // "Witaj, Gość"
Zasada: gdy łączysz ? : z innymi operatorami, weź warunek w nawias.
Mój kod nie działa! Dlaczego?
Czy pusty obiekt i pusta tablica to wartości prawdziwe (truthy)?
Pusty obiekt {} i pusta tablica [] to nie wartość fałszywa. Jeśli więc np. pobierasz dane przez API i chcesz powstrzymać operacje na danych, jeśli otrzymasz pusty obiekt, to trzeba rozwiązać to inaczej, np. sprawdzić, czy obiekt ma klucze. Jeśli jest opcja otrzymania pustej tablicy, możesz sprawdzić, czy jej długość jest większa od 0:
const data = {}
const list = []
if (Object.keys(data).length > 0) { /* obiekt ma jakieś dane */ }
if (list.length > 0) { /* tablica ma jakieś elementy */ }
Dodatkowe materiały: jak sprawdzić, czy obiekt ma klucze.
Czy kolejność sprawdzania warunków ma znaczenie?
Pamiętaj, że decyduje kolejność sprawdzania warunków (od lewej do prawej) oraz że mają one priorytety. Jeżeli więc np. uzależniasz uruchomienie funkcji od innej zmiennej, to funkcja ta może się nigdy nie wykonać:
const newsletterBox = isLoggedIn && showNewsletterBox()
Funkcja showNewsletterBox() nie zostanie uruchomiona, jeżeli zmienna isLoggedIn przechowuje wartość falsy. Świadomość tego pomoże Ci przy debugowaniu – zamiast szukać przyczyny w showNewsletterBox() (w końcu nie widzimy efektów jej działania, więc „to pewnie z nią jest coś nie tak"), warto sprawdzić, czy przypadkiem z jakiegoś powodu zmienna isLoggedIn nie ma ciągle przypisywanej wartości fałszywej.
Osobna pułapka dotyczy ??. Nie wolno mieszać go z && ani || bez nawiasów – JavaScript zgłosi SyntaxError:
const result = a || b ?? c // SyntaxError
const fixed = (a || b) ?? c // OK
Jak pogrupować warunki za pomocą nawiasów okrągłych?
Jeżeli wyrażenie logiczne jest długie i nie jesteś już pewien, co zwrócą konkretne fragmenty, a priorytety Ci się mieszają, użyj nawiasów okrągłych:
playerType === "beginner" && isLoggedIn() || isSessionContinued() || !isGameEnd && score !== 0
// pogrupowany kod:
(playerType === "beginner" && isLoggedIn()) || isSessionContinued() || (!isGameEnd && score !== 0)
W takim rozbudowanym przypadku lepiej byłoby jednak pomyśleć o przeniesieniu wyrażenia logicznego do osobnej funkcji.
Jak rozbić długi warunek na krótsze części?
Staraj się nie tworzyć długich wyrażeń logicznych, ale jeżeli już je masz i widzisz, że kod z instrukcji warunkowej się nie wykonuje, to w debugowaniu pomoże Ci np.:
- wyświetlenie w konsoli każdej części wyrażenia – być może któraś z nich zwraca nie to, czego się spodziewasz,
- dodatkowo wrzucenie tych fragmentów do funkcji
Boolean()– wówczas nie będzie potrzeby głowić się, czy na pewno dobrze przewidujemy wartość wyrażenia, tylko od razu otrzymamytruelubfalse:
console.log(Boolean(nameValue.length > 0)) // true lub false
console.log(Boolean(emailValue.includes('@'))) // true lub false
Warto też wziąć kartkę i długopis i rozrysować sobie wyrażenia logiczne. Zastanów się, co zwróci każdy z operatorów, jaki ma priorytet i jak zostanie zinterpretowane to od lewej.
Dlaczego warto unikać negacji w nazwach zmiennych?
Staraj się unikać nazw typu isNotLoaded, ponieważ później użycie tego w instrukcji z operatorem NOT komplikuje sprawę:
if (!isNotLoaded) {
console.log("Operation…?")
}
I co teraz? Czy jeśli isNotLoaded przechowuje true, to znaczy, że dane nie zostały załadowane? Skoro teraz temu zaprzeczymy operatorem !, to w warunku będzie false i instrukcja się nie wykona. Chyba – bo dla zmęczonej kodowaniem głowy nic już nie będzie oczywiste. Nie warto więc komplikować sobie rozwiązań. W powyższym przypadku nazwanie zmiennej isLoaded rozwiązałoby problem.
Co dalej?
Rozumienie operatorów logicznych przyjdzie wraz z praktyką. Warto jednak wracać do tego artykułu, gdy napotkasz problemy. Na początku nauki mogą jeszcze mylić Ci się priorytety operatorów czy sposób interpretacji wyrażeń logicznych. W razie czego zawsze miej pod ręką console.log() i funkcję Boolean().


