Uwaga! Trwają prace nad nową wersją serwisu. Mogą występować przejściowe problemy z jego funkcjonowaniem. Przepraszam za niedogodności!

⛔ Potrzebujesz wsparcia? Oceny CV? A może Code Review? ✅ Dołącz do naszej społeczności na Discordzie!

Operatory logiczne w JavaScript – sposoby użycia i błędy

Instrukcje warunkowe, dynamiczne przypisywanie wartości i korzystanie z funkcji

Dzięki zrozumieniu operatorów logicznych dużo łatwiej będzie Ci debugować swoje rozwiązania. Gdy nauczysz się przenosić wyrażenia logiczne do funkcji, Twój kod zyska na czytelności. Ten artykuł pomoże Ci usystematyzować wiedzę z zakresu łączenia operatorów logicznych w JS i poznać przypadki ich zastosowania.

Spis treści

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”.

 Stosowanie zmiennych 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 opertor 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

 Wartość zwracana z funkcji w wyrażeniach logicznych

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 funkcji 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”.

 Łączenie operatorów logicznych

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.

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żenia 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

Tu już widać, że otrzymamy 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

 Instrukcja warunkowa

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 z wyrażenia logicznego zamieszczonego w zmiennej isWinner
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.contains('@') &&
  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.contains('@') &&
    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ć jakieś akcję zależną tylko od tego, że pola nie są wypełnione nieprawidł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.');
}

 Dynamiczne przypisywanie wartości do zmiennych

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

Spójrz na poniższy przykład:

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. autor kodu zwraca null).

Dzięki temu ustawimy 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')

Operator OR

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.

const articleElement.style.color = themeColor || "black"

W przykładzie, jeśli nie będziemy mieć danych o kolorze z motywu w zmiennej themeColor (np. zabraknie odpowiedniego pliku i zmienna themeColor będzie przechowywać wartość falsy), to ustawi się kolor czarny. Służy to jako backup.

 Mój kod nie działa! Dlaczego?

 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.

 Sprawdź kolejność interpretacji kodu

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 user = 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.

 Pogrupuj 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 === "begginer" && isLoggedIn() || isSessionContinued() || !isGameEnd && score !== 0

// pogrupowany kod:
( playerType === "begginer" && isLoggedIn() ) || isSessionContinued() || ( !isGameEnd && score !== 0 )

W takim rozbudowanym przypadku lepiej byłoby jednak pomyśleć o przeniesieniu wyrażenia logicznego do osobnej funkcji.

 Rozbij 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 to funkcji Boolean() – wówczas nie będzie potrzeby głowić się, czy na pewno dobrze przewidujemy wartość wyrażenia, tylko od razu otrzymamy 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.

 Przemyśl negację – operator NOT

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.

 

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().

Udostępnij ten artykuł:

Mentoring to efektywna nauka pod okiem doświadczonej osoby, która:

  • przekazuje Ci swoją wiedzę i nadzoruje Twoje postępy w zdobywaniu umiejętności,
  • uczy Cię dobrych praktyk i wyłapuje złe nawyki,
  • wspiera Twój rozwój i zwiększa zaangażowanie w naukę.

Mam coś dla Ciebie!

W każdy piątek rozsyłam motywujący do nauki programowania newsletter!

Dodatkowo od razu otrzymasz ode mnie e-book o wartości 39 zł. To ponad 40 stron konkretów o nauce programowania i pracy w IT.

PS Zazwyczaj wysyłam 1-2 wiadomości na tydzień. Nikomu nie będę udostępniał Twojego adresu e-mail.