- nowość
- W empik go
Javascript od pierwszej linijki. Naucz się jak pisać gry, strony WWW i aplikacje internetowe! - ebook
Javascript od pierwszej linijki. Naucz się jak pisać gry, strony WWW i aplikacje internetowe! - ebook
Opanowanie Javascript stanowi dzisiaj kluczową kompetencję w dynamicznie rozwijającej się branży technologicznej. Ta kompleksowa publikacja systematycznie wprowadza czytelnika w świat nowoczesnego programowania webowego, koncentrując się na praktycznych zastosowaniach. Publikacja szczegółowo analizuje współczesne aspekty rozwoju front-endu, w tym: - Asynchroniczne przetwarzanie danych i komunikację z API - Optymalizację wydajności - Implementację responsywnych interfejsów - Testowanie i debugowanie kodu - Integrację z bibliotekami i frameworkami Materiał został zorganizowany w logiczną ścieżkę rozwoju kompetencji. Szczególną wartość stanowią rozbudowane przykłady kodu, które ilustrują praktyczne zastosowanie omawianych koncepcji. Systematyczne podejście do nauki, połączone z praktycznymi projektami i rzeczywistymi przykładami implementacji, pozwala czytelnikom na budowanie portfolio projektów, które mogą stanowić wartościowy punkt wyjścia do kariery w branży deweloperskiej.
Kategoria: | Programowanie |
Zabezpieczenie: |
Watermark
|
ISBN: | 9788368316209 |
Rozmiar pliku: | 210 KB |
FRAGMENT KSIĄŻKI
Wprowadzenie do JavaScript
Historia języka i jego znaczenie
Gdzie można używać JavaScriptu
Pierwsze "Hello World"
Konsola przeglądarki i podstawy debugowania
Dodawanie skryptów do strony HTML
Zmienne i typy danych
let, const i var
String, Number, Boolean
undefined i null
Konwersja typów
Symbol i BigInt
Przykłady deklaracji i inicjalizacji
Operatory i wyrażenia
Operatory arytmetyczne (+, -, \*, /)
Operatory porównania (==, ===, !=, !==)
Operatory logiczne (&&, ||, !)
Operator warunkowy (ternary)
Operatory przypisania (+=, -=, \*=, /=)
Priorytet operatorów
Instrukcje warunkowe
if, else if, else
switch/case
Zagnieżdżone warunki
Praktyczne przykłady z walidacją formularzy
Obsługa błędów try/catch
Pętle i iteracje
Pętla for
Pętla while i do...while
forEach dla tablic
for...in dla obiektów
for...of dla iterowalnych
break i continue
Funkcje
Deklaracja funkcji
Wyrażenia funkcyjne
Funkcje strzałkowe
Parametry i argumenty
Zwracanie wartości
Scope i closure
Funkcje callback
Rekurencja
Tablice i ich metody
Tworzenie i modyfikacja tablic
push, pop, shift, unshift
map, filter, reduce
sort i reverse
slice i splice
Tablice wielowymiarowe
Praktyczne przykłady przetwarzania danych
Obiekty i programowanie obiektowe
Tworzenie obiektów
Właściwości i metody
Prototypy i dziedziczenie
Klasy w ES6+
Gettery i settery
This i wiązanie kontekstu
Przykłady projektowania obiektowego
Metody obsługi stringów
Konkatenacja i template literals
indexOf i includes
slice, substring, split
toLowerCase i toUpperCase
replace i replaceAll
Wyrażenia regularne
Praktyczne przykłady formatowania tekstu
Zdarzenia i interakcje z użytkownikiem
addEventListener
Typy zdarzeń (click, submit, keyup)
Event bubbling i capturing
preventDefault i stopPropagation
Tworzenie własnych zdarzeń
Praktyczne przykłady interakcji
Manipulacja elementami DOM
Selektory (getElementById, querySelector)
Modyfikacja zawartości (innerHTML, textContent)
Nawigacja po DOM
Praktyczne przykłady dynamicznej zawartości
Asynchroniczność i Promise
setTimeout i setInterval
Callbacks
Promise i łańcuchy then
async/await
Obsługa błędów w kodzie asynchronicznym
Przykłady równoległego wykonywania zadań
Fetch API i komunikacja z serwerem
GET, POST, PUT, DELETE
Wysyłanie i odbieranie danych
Headers i opcje fetch
Obsługa odpowiedzi JSON
Obsługa błędów sieciowych
Przykłady komunikacji z REST API
Storage i zarządzanie danymi
localStorage i sessionStorage
JSON.stringify i JSON.parse
Cookies
IndexedDB podstawy
Przykłady persist
Security i Data Privacy
Performance Optimization
Obsługa formularzy
Walidacja pól
Obsługa submit
Dynamiczne formularze
File upload
Autouzupełnianie
Przykłady formularzy kontaktowych
Form State Management
Debugowanie i narzędzia deweloperskie
Console methods
Breakpoints
Network tab
Performance profiling
Memory leaks
Przykłady debugowania typowych problemów
Source Maps
Optymalizacja i dobre praktyki
Clean code
DRY i SOLID
Optymalizacja wydajności
Code review
Przykłady refaktoryzacji
Error Handling
Frameworki i biblioteki JavaScript
Wprowadzenie do React
Podstawy Vue
jQuery w nowoczesnym JS
Porównanie popularnych rozwiązań
Kiedy używać frameworków
Tworzenie pierwszej gry
Game loop
Obsługa kolizji
Animacje
Sterowanie
Punktacja
Przykład prostej gry platformowej
Audio
Debug i Testing
Tworzenie aplikacji internetowej
Architektura aplikacji
Routing
Stan aplikacji
Komponenty wielokrotnego użytku
Deployment
Przykład Todo App
TestingHistoria języka i jego znaczenie
JavaScript narodził się w bardzo ciekawym momencie rozwoju internetu. W 1995 roku programista Brendan Eich, pracujący dla firmy Netscape, otrzymał niezwykle ambitne zadanie - stworzenie języka programowania, który działałby bezpośrednio w przeglądarce internetowej. W ciągu zaledwie 10 dni stworzył pierwszą wersję języka, początkowo nazwanego LiveScript. Był to moment przełomowy, ponieważ strony internetowe przestały być tylko statycznymi dokumentami. Gdy firma Netscape nawiązała współpracę z firmą Sun Microsystems (twórcą popularnego wówczas języka Java), LiveScript zmienił nazwę na JavaScript - był to sprytny zabieg marketingowy, choć oba języki nie miały ze sobą wiele wspólnego. W 1997 roku, aby uporządkować rozwój języka, powstał standard ECMAScript. JavaScript stał się jego najbardziej znaną implementacją, a organizacja ECMA International zajęła się dalszym rozwojem specyfikacji.
Początkowo JavaScript był niedoceniany przez programistów. Służył głównie do prostych zadań, takich jak walidacja formularzy czy tworzenie efektów wizualnych na stronach. Jednak z biegiem lat jego możliwości znacząco wzrosły. Prawdziwa rewolucja nastąpiła w 2009 roku wraz z powstaniem Node.js - środowiska pozwalającego uruchamiać JavaScript poza przeglądarką. Nagle okazało się, że ten sam język może służyć do tworzenia zarówno interfejsu użytkownika, jak i logiki serwerowej. Rozwój technologii webowych sprawił, że pojawiły się potężne frameworki frontendowe, umożliwiające tworzenie zaawansowanych aplikacji działających w przeglądarce. JavaScript przestał być tylko dodatkiem do HTML-a, a stał się pełnoprawną platformą programistyczną.
Dziś JavaScript jest jednym z najpopularniejszych języków programowania na świecie. Według indeksu TIOBE regularnie znajduje się w pierwszej dziesiątce, a na GitHubie jest językiem z największą liczbą repozytoriów. Jego wszechstronność jest imponująca - służy do tworzenia interfejsów użytkownika, aplikacji serwerowych, aplikacji mobilnych (poprzez frameworki hybrydowe), a nawet aplikacji desktopowych. W zasadzie każda nowoczesna strona internetowa wykorzystuje JavaScript, a znajomość tego języka jest jednym z podstawowych wymagań na stanowiskach związanych z tworzeniem oprogramowania.
Gdzie można używać JavaScriptu
JavaScript to wyjątkowy język, który może działać w różnych środowiskach wykonawczych. Najpopularniejszym z nich jest oczywiście przeglądarka internetowa - każda nowoczesna przeglądarka posiada wbudowany silnik JavaScript, który interpretuje i wykonuje kod. Jednym z najważniejszych silników jest V8, stworzony przez Google, który napędza nie tylko przeglądarkę Chrome, ale stał się również podstawą Node.js. Node.js to środowisko, które pozwala uruchamiać JavaScript poza przeglądarką, bezpośrednio na komputerze czy serwerze. To właśnie dzięki Node.js JavaScript wyrwał się z ram przeglądarki i może być używany praktycznie wszędzie.
Różnorodność środowisk wykonawczych przekłada się na szerokie spektrum aplikacji, jakie możemy tworzyć w JavaScript. W przeglądarce możemy budować zarówno proste strony internetowe wzbogacone o interaktywne elementy, jak i złożone aplikacje webowe działające jak programy desktopowe. Po stronie serwera, dzięki Node.js, możemy tworzyć wydajne API RESTowe obsługujące tysiące zapytań. JavaScript świetnie sprawdza się też w tworzeniu aplikacji desktopowych - technologia Electron pozwala pakować aplikacje webowe w samodzielne programy działające na komputerze. W świecie mobile, dzięki technologiom jak React Native, możemy tworzyć natywne aplikacje mobilne używając znajomej składni JavaScriptu.
Do tworzenia aplikacji w JavaScript mamy do dyspozycji bogaty zestaw narzędzi programistycznych. Podstawowym narzędziem jest edytor kodu - od prostych jak Visual Studio Code po zaawansowane środowiska IDE jak WebStorm. Nieocenioną pomocą są narzędzia deweloperskie wbudowane w przeglądarki internetowe - pozwalają one na debugowanie kodu, analizę wydajności, podgląd zmiennych i struktury DOM. Console w narzędziach deweloperskich to pierwsze miejsce, gdzie zwykle testujemy nasz kod i sprawdzamy jego działanie. Większość edytorów oferuje kolorowanie składni, autouzupełnianie i podpowiedzi, co znacząco ułatwia pisanie kodu JavaScript.
// Przykład kodu, który możemy przetestować w konsoli przeglądarki
console.log("Hello World!");
// Prosty przykład interakcji z DOM-em
document.querySelector('button').addEventListener('click', () => {
alert('Przycisk został kliknięty!');
});
// Przykład kodu Node.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end('Hello World!');
});
server.listen(8080);
Pierwsze "Hello World"
Zacznijmy od stworzenia najprostszej strony internetowej z JavaScript. Kod JavaScript możemy umieścić w pliku HTML na kilka sposobów. Najpopularniejszym jest użycie znacznika script w sekcji head lub na końcu body dokumentu. Możemy też napisać kod JavaScript bezpośrednio między znacznikami script lub wskazać zewnętrzny plik .js. Oto przykład prostej strony z osadzonym kodem:
Hello World!
JavaScript oferuje kilka podstawowych sposobów wyświetlania danych. Najprostszym jest użycie funkcji console.log(), która wyświetla informacje w konsoli przeglądarki - jest to podstawowe narzędzie podczas nauki i debugowania kodu. Funkcja alert() wyświetla okienko z komunikatem, które użytkownik musi zaakceptować. Z kolei document.write() pozwala wpisać treść bezpośrednio do dokumentu HTML. Oto przykłady użycia każdej z tych metod:
// Wyświetlenie w konsoli
console.log("Hello World w konsoli!");
// Wyświetlenie alertu
alert("Hello World w alercie!");
// Wyświetlenie na stronie
document.write("Hello World na stronie!");
// Możemy też wyświetlać liczby i wyrażenia
console.log(2 + 2); // wyświetli 4
console.log("Wynik:", 2 + 2); // wyświetli "Wynik: 4"
Pisząc kod JavaScript, warto od początku przywyknąć do pewnych zasad. Instrukcje kończymy średnikiem (;) - choć JavaScript często wybaczy nam jego brak, to jest to dobra praktyka. Kod warto formatować używając wcięć (najczęściej 2 lub 4 spacje) dla lepszej czytelności. Komentarze są niezwykle ważne - pomagają nam i innym zrozumieć kod. Możemy używać komentarzy jednoliniowych rozpoczynających się od // lub wieloliniowych między /\* a \*/. Przykład:
// To jest komentarz jednoliniowy
console.log("Hello"); // To też jest komentarz jednoliniowy
/* To jest komentarz wieloliniowy, który może zajmować wiele linii */
/* Przykład wcięć w kodzie */
if (true) {
console.log("Pierwsza linia");
console.log("Druga linia");
}Konsola przeglądarki i podstawy debugowania
Zacznijmy od tego, jak dotrzeć do konsoli przeglądarki. W większości nowoczesnych przeglądarek możemy ją otworzyć naciskając F12 lub kombinację Ctrl+Shift+I (Cmd+Option+I na MacOS). Możemy też kliknąć prawym przyciskiem myszy na stronie i wybrać "Zbadaj element" lub "Inspect", a następnie przejść do zakładki "Console". Konsola oferuje różne typy komunikatów, które różnią się wizualnie i ważnością: zwykłe logi (console.log), informacje (console.info), ostrzeżenia (console.warn) i błędy (console.error). Każdy z nich ma inne przeznaczenie i jest oznaczony inną ikoną, co pomaga w szybkiej identyfikacji typu komunikatu.
Konsola JavaScript oferuje wiele przydatnych metod, które ułatwiają debugowanie kodu. Podstawowa metoda console.log() wyświetla przekazane wartości w najprostszej formie. Metoda console.table() jest świetna do wyświetlania danych tabelarycznych - automatycznie formatuje tablice i obiekty w czytelną tabelę. Console.group() i console.groupEnd() pozwalają grupować powiązane komunikaty, co jest szczególnie przydatne przy debugowaniu większych fragmentów kodu. Metody console.time() i console.timeEnd() służą do mierzenia czasu wykonania kodu. Oto przykłady:
// Różne typy komunikatów
console.log("Zwykły komunikat");
console.info("Informacja");
console.warn("Ostrzeżenie!");
console.error("Błąd!");
// Wyświetlanie danych w tabeli
const users = ;
console.table(users);
// Grupowanie komunikatów
console.group("Testowanie funkcji");
console.log("Test 1 passed");
console.log("Test 2 passed");
console.groupEnd();
// Mierzenie czasu
console.time("Pętla");
for (let i = 0; i < 1000000; i++) {}
console.timeEnd("Pętla");
Kiedy w naszym kodzie występuje błąd, konsola jest pierwszym miejscem, gdzie powinniśmy szukać informacji o problemie. Błędy w JavaScript zawierają nazwę błędu (np. SyntaxError, ReferenceError, TypeError) oraz opis problemu. Stack trace (stos wywołań) pokazuje dokładną ścieżkę, która doprowadziła do błędu - od miejsca jego wystąpienia, przez wszystkie funkcje, które były po drodze wywołane. Najczęstsze błędy składniowe to brakujące nawiasy, przecinki czy średniki, literówki w nazwach zmiennych lub próba użycia zmiennej, która nie została zadeklarowana. Przykładowo:
// Przykład błędu składniowego
function test() {
console.log("Start");
undefinedVariable; // ReferenceError: undefinedVariable is not defined
console.log("Koniec");
}
// Przykład błędu typu
const number = 42;
number.toUppercase(); // TypeError: number.toUppercase is not a function
// Przykład błędu składni
if (true { // SyntaxError: missing ) after condition
console.log("Błąd!");
}
Dodawanie skryptów do strony HTML
Umieszczenie skryptu JavaScript w dokumencie HTML ma kluczowe znaczenie dla działania naszej strony. Możemy umieścić skrypt w sekcji head lub na końcu body - każde rozwiązanie ma swoje zalety. Skrypty w head są ładowane wcześniej, ale mogą opóźnić renderowanie strony. Skrypty na końcu body pozwalają stronie załadować się szybciej, ale kod wykona się później. Atrybuty async i defer dają nam dodatkową kontrolę: async powoduje asynchroniczne ładowanie skryptu, a defer odkłada wykonanie do momentu załadowania całego HTML-a. Oto przykłady różnych sposobów dodawania skryptów:
Przechowywanie kodu JavaScript w zewnętrznych plikach .js ma wiele zalet. Przede wszystkim poprawia to czytelność i utrzymanie kodu - łatwiej znaleźć i poprawić błędy w osobnych plikach. Dodatkowo, przeglądarka może cachować zewnętrzne pliki JS, co przyspieszy ładowanie strony przy kolejnych wizytach. Podczas linkowania plików możemy używać ścieżek względnych (relatywnych do aktualnego pliku HTML) lub bezwzględnych (pełny URL). Przykład struktury projektu i linkowania:
Przy organizacji kodu JavaScript warto kierować się kilkoma zasadami. Dla małych projektów jeden plik może wystarczyć, ale w większych projektach lepiej podzielić kod na mniejsze, tematyczne pliki. Ważna jest kolejność ładowania - jeśli jeden skrypt zależy od drugiego (np. używa jego funkcji), musi być załadowany po nim. Dobrą praktyką jest umieszczanie bibliotek zewnętrznych przed własnym kodem. Przykładowa struktura:
Gdy skrypty nie ładują się poprawnie, pierwszym krokiem powinno być sprawdzenie konsoli przeglądarki. Błąd 404 oznacza, że plik nie został znaleziony - warto wtedy sprawdzić czy ścieżka jest poprawna i czy plik faktycznie istnieje w podanej lokalizacji. Jeśli skrypt się ładuje, ale nie działa poprawnie, może to oznaczać problem z kolejnością ładowania - na przykład próbujemy użyć funkcji z biblioteki, która jeszcze się nie załadowała. Pomocne jest sprawdzenie zakładki Network w narzędziach deweloperskich, która pokazuje kolejność i status ładowania wszystkich plików:
let, const i var
W JavaScript mamy trzy sposoby deklarowania zmiennych: let, const i var. Każdy z nich ma swoje unikalne cechy i zachowania, które warto dobrze zrozumieć. Zacznijmy od podstawowych różnic między nimi.
Kiedy deklarujemy zmienną za pomocą let, tworzymy zmienną która może być modyfikowana, ale jest ograniczona do bloku kodu w którym została zadeklarowana. Blok kodu to wszystko co znajduje się między nawiasami klamrowymi {}. Z kolei const służy do tworzenia stałych - wartości które nie mogą być zmieniane po pierwszym przypisaniu. Najstarszy sposób, czyli var, tworzy zmienne które są widoczne w całej funkcji, niezależnie od bloków kodu.
let wiek = 25;
wiek = 26; // OK - można zmienić
const imie = "Marek";
// imie = "Tomek"; // Błąd! Stałej nie można zmienić
var miasto = "Warszawa";
{
var miasto = "Kraków"; // Uwaga - nadpisze poprzednią wartość!
}
console.log(miasto); // Wyświetli "Kraków"
Ważną cechą zmiennych jest ich zakres (scope). Let i const mają zakres blokowy - są widoczne tylko wewnątrz bloku w którym je zadeklarowano. Var natomiast ma zakres funkcyjny - jest widoczny w całej funkcji, nawet poza blokami. Co więcej, var podlega tzw. hoistingowi - deklaracje var są "wyciągane" na początek funkcji, co może prowadzić do nieoczekiwanych zachowań.
function przyklad() {
{
let x = 1;
const y = 2;
var z = 3;
}
// console.log(x); // Błąd - x nie jest dostępne
// console.log(y); // Błąd - y nie jest dostępne
console.log(z); // Działa! Wyświetli 3
}
Przejdźmy teraz do praktycznych zastosowań. W nowoczesnym JavaScripcie najczęściej używamy const dla wartości które nie powinny się zmieniać - na przykład dla konfiguracji, stałych matematycznych czy referencji do elementów DOM. Let stosujemy gdy wiemy że zmienna będzie modyfikowana - świetnie sprawdza się w pętlach, licznikach czy do przechowywania tymczasowych wartości.
const PI = 3.14159;
const przyciskLogowania = document.getElementById('login');
let licznikKlikniec = 0;
let aktualnyPoziom = 1;
// Unikamy var w nowoczesnym kodzie
// var staraZmienna = "przestarzałe"; // Tak nie robimy!
Var jest obecnie uznawany za przestarzały i najlepiej go unikać. Jego dziwne zachowanie związane z hoistingiem i zakresem funkcyjnym może prowadzić do trudnych do wyśledzenia błędów. Zamiast tego zawsze używaj let dla zmiennych które będą modyfikowane, i const dla wartości stałych.
Na koniec przyjrzyjmy się typowym błędom. Pierwszym jest próba ponownej deklaracji zmiennej w tym samym zakresie:
let wynik = 10;
let wynik = 20; // Błąd! Nie można zadeklarować dwa razy
const stala = 5;
stala = 6; // Błąd! Nie można zmienić stałej
// Problem z hoistingiem
console.log(zmienna); // undefined, ale nie błąd!
var zmienna = "hoist";
Próba modyfikacji stałej zadeklarowanej przez const zawsze kończy się błędem. Pamiętaj jednak, że jeśli stała przechowuje obiekt, jego właściwości nadal można modyfikować! Tylko sama referencja do obiektu jest niezmienna:
const gracz = { imie: "Jan", punkty: 0 };
gracz.punkty = 100; // To zadziała!
// gracz = {}; // Ale to wywoła błąd
String, Number, Boolean
JavaScript posiada trzy podstawowe typy prymitywne, które używamy najczęściej: string (tekst), number (liczba) i boolean (wartość logiczna prawda/fałsz). Stringi tworzymy używając cudzysłowów lub apostrofów, liczby zapisujemy bezpośrednio jako cyfry, a wartości logiczne to słowa kluczowe true i false.
let tekst = "Hello World";
let innyTekst = 'Cześć świecie';
let liczba = 42;
let liczbaUjemna = -17.5;
let prawda = true;
let falsz = false;
Przejdźmy teraz do operacji na tych typach. Stringi możemy łączyć za pomocą operatora + lub używając nowocześniejszych template literals (znaczników \`). Z liczbami możemy wykonywać podstawowe działania matematyczne, a wartości logiczne możemy porównywać używając operatorów porównania.
// Operacje na stringach
let imie = "Jan";
let nazwisko = "Kowalski";
let pelneImie = imie + " " + nazwisko;
let przedstawienie = `Nazywam się ${imie} ${nazwisko}`;
// Operacje na liczbach
let suma = 5 + 3;
let roznica = 10 - 4;
let iloczyn = 6 * 2;
let iloraz = 15 / 3;
let potega = Math.pow(2, 3);
let pierwiastek = Math.sqrt(16);
// Operacje na wartościach logicznych
let wiekszy = 10 > 5;
let rowny = 7 === 7;
let nieRowny = 8 !== 3;
JavaScript ma pewne specyficzne zachowania związane z liczbami zmiennoprzecinkowymi. Ze względu na sposób ich przechowywania, niektóre obliczenia mogą dawać nieoczekiwane wyniki. Dodatkowo mamy specjalne wartości jak NaN (Not a Number) czy Infinity.
let dziwneDzialanie = 0.1 + 0.2;
console.log(dziwneDzialanie); // 0.30000000000000004
let niemozliweDzielenie = 10 / 0;
console.log(niemozliweDzielenie); // Infinity
let bledneObliczenie = "tekst" * 2;
console.log(bledneObliczenie); // NaN
// Sprawdzanie NaN
console.log(isNaN(bledneObliczenie)); // true
Na koniec przyjrzyjmy się typowym pułapkom związanym z typami podstawowymi. Pierwszą jest problem z porównywaniem stringów zawierających liczby:
let liczbaJakoString = "123";
let liczba = 123;
console.log(liczbaJakoString == liczba); // true (!)
console.log(liczbaJakoString === liczba); // false (bezpieczniejsze)
// Problemy z działaniami na stringach i liczbach
let dziwneDodawanie = "5" + 3; // "53"
let dziwneOdejmowanie = "5" - 3; // 2 (!)
// Pułapki z wartościami logicznymi
let pustyString = "";
let zero = 0;
if (!pustyString) console.log("pusty string jest false!");
if (!zero) console.log("zero jest false!");undefined i null
Undefined i null to dwie specjalne wartości w JavaScript, które oznaczają brak wartości, ale robią to w nieco inny sposób. Undefined otrzymujemy automatycznie gdy zmienna została zadeklarowana, ale nie przypisano jej żadnej wartości. JavaScript również zwraca undefined gdy próbujemy odczytać nieistniejącą właściwość obiektu lub gdy funkcja nie ma jawnie zdefiniowanego return. Z kolei null to wartość, którą programista przypisuje świadomie, gdy chce zaznaczyć że coś celowo nie ma wartości lub że obiekt jest pusty.
let nieprzypisanaZmienna;
console.log(nieprzypisanaZmienna); // undefined
function funkcjaBezReturn() {
let x = 1 + 1;
}
console.log(funkcjaBezReturn()); // undefined
let celowoPusta = null;
console.log(celowoPusta); // null
Do sprawdzania czy coś jest undefined lub null mamy kilka sposobów. Najprostszym jest użycie operatora typeof dla undefined lub bezpośrednie porównanie dla null. Przy sprawdzaniu właściwości obiektów warto używać bezpiecznych metod, takich jak operator opcjonalnego łańcuchowania (?.).
// Sprawdzanie undefined
let cos;
console.log(typeof cos === "undefined"); // true
// Sprawdzanie null
let pusto = null;
console.log(pusto === null); // true
// Bezpieczne sprawdzanie właściwości
let uzytkownik = { profil: { imie: "Jan" } };
// Bezpiecznie - nie wywoła błędu
console.log(uzytkownik?.profil?.wiek); // undefined
// Sprawdzanie obu przypadków
console.log(pusto ?? "wartość domyślna"); // "wartość domyślna"
console.log(undefined ?? "wartość domyślna"); // "wartość domyślna"
Jednym z najczęstszych błędów jest próba wywołania metody lub dostępu do właściwości na undefined lub null. Taka operacja zawsze kończy się błędem TypeError. Aby tego uniknąć, zawsze warto sprawdzać czy obiekt istnieje przed próbą dostępu do jego właściwości.
let obiekt = null;
// Tak nie robimy - wywoła błąd
// console.log(obiekt.wlasciwosc); // TypeError!
// Tak jest bezpiecznie
if (obiekt) {
console.log(obiekt.wlasciwosc);
}
// Lub jeszcze bezpieczniej z operatorem ?.
console.log(obiekt?.wlasciwosc);
// Unikanie błędów przy funkcjach
let tablica = ;
let funkcja = undefined;
// Bezpieczne wywołanie funkcji
funkcja?.(); // nie wywoła błędu, po prostu zwróci undefined
// Bezpieczne operacje na tablicach
console.log(tablica?.); // undefined zamiast błędu
Konwersja typów
W JavaScript możemy zmieniać typ danych na dwa sposoby: jawnie (czyli świadomie używając odpowiednich funkcji) lub niejawnie (gdy JavaScript sam konwertuje typy podczas operacji). Do jawnej konwersji używamy funkcji String(), Number() i Boolean(). JavaScript też automatycznie konwertuje typy gdy używamy operatorów lub porównujemy wartości - to właśnie nazywamy niejawną konwersją (type coercion).
// Jawna konwersja
let liczba = 42;
let tekstZLiczby = String(liczba); // "42"
let logicznaZLiczby = Boolean(liczba); // true
let tekst = "123";
let liczbaZTekstu = Number(tekst); // 123
let logicznaZTekstu = Boolean(tekst); // true
let wartosc = true;
let tekstZLogicznej = String(wartosc); // "true"
let liczbaZLogicznej = Number(wartosc); // 1
JavaScript stosuje różne reguły podczas konwersji typów. Operator + zachowuje się inaczej niż pozostałe operatory matematyczne - jeśli choć jeden z operandów jest stringiem, drugi zostanie przekonwertowany na string. Z kolei operator == próbuje konwertować wartości do wspólnego typu przed porównaniem. Funkcje parseInt i parseFloat służą do bezpieczniejszej konwersji stringów na liczby, pozwalając określić system liczbowy i radząc sobie z tekstem mieszanym z cyframi.
// Różne zachowania operatorów
console.log("5" + 3); // "53" (konkatenacja)
console.log("5" - 3); // 2 (konwersja na liczby)
console.log("5" == 5); // true (niejawna konwersja)
console.log("5" === 5); // false (bez konwersji)
// parseInt i parseFloat
console.log(parseInt("42px")); // 42
console.log(parseFloat("3.14witaj")); // 3.14
console.log(parseInt("0xFF", 16)); // 255 (system szesnastkowy)
Konwersja typów w JavaScript może prowadzić do nieoczekiwanych wyników, jeśli nie rozumiemy jak działa. Oto najczęstsze pułapki i sposób jak ich unikać. Zawsze używaj === zamiast == gdy nie chcesz niejawnej konwersji typów. Przy operacjach na liczbach zawsze upewnij się, że pracujesz na właściwym typie danych. Przy łączeniu stringów z innymi typami, jawnie konwertuj wartości na string.
// Pułapki konwersji typów
console.log( + ); // "" (pusta string)
console.log( + {}); // ""
console.log(true + true); // 2
console.log("3" + 1); // "31"
console.log("3" - 1); // 2
// Dobre praktyki
let wiek = "20";
// Źle:
let zaRok = wiek + 1; // "201"
// Dobrze:
let zaRokPoprawnie = Number(wiek) + 1; // 21
// Bezpieczne porównania
let x = 0;
let y = "0";
// Źle:
if (x == y) { } // true
// Dobrze:
if (x === y) { } // false
Symbol i BigInt
Symbol to specjalny typ danych w JavaScript, który służy do tworzenia unikalnych identyfikatorów. Każdy utworzony Symbol jest gwarantowanie unikalny, nawet jeśli ma ten sam opis. Jest to szczególnie przydatne gdy chcemy dodać unikalne właściwości do obiektów lub stworzyć specjalne identyfikatory, które nigdy się nie powtórzą. Symbol.for() pozwala nam tworzyć symbole współdzielone w globalnym rejestrze symboli.
// Tworzenie podstawowych symboli
let symbol1 = Symbol('identyfikator');
let symbol2 = Symbol('identyfikator');
console.log(symbol1 === symbol2); // false - są unikalne!
// Użycie Symbol.for()
let wspoldzielonySymbol1 = Symbol.for('wspoldzielony');
let wspoldzielonySymbol2 = Symbol.for('wspoldzielony');
console.log(wspoldzielonySymbol1 === wspoldzielonySymbol2); // true
// Użycie jako klucza w obiekcie
let obiekt = { : "ukryta wartość", normalnaWlasciwosc: "widoczna wartość" };
BigInt to stosunkowo nowy typ danych, który pozwala na pracę z liczbami większymi niż standardowy limit Number (2^53 - 1). Tworzymy go dodając 'n' na końcu liczby lub używając funkcji BigInt(). BigInty mogą być używane do większości operacji matematycznych, ale tylko z innymi BigIntami - nie możemy ich mieszać ze zwykłymi liczbami.
// Tworzenie BigInt
let duzaLiczba = 9007199254740991n;
let innaMetoda = BigInt("9007199254740991");
// Operacje na BigInt
let suma = duzaLiczba + 1n;
let iloczyn = duzaLiczba * 2n;
// Konwersja między BigInt a Number
let zwyklaLiczba = Number(duzaLiczba);
let spowrotemBigInt = BigInt(zwyklaLiczba);
// Nie można mieszać typów!
// let blednaOperacja = duzaLiczba + 1; // TypeError!
W praktyce, Symbole są często używane w bibliotekach i frameworkach do tworzenia unikalnych identyfikatorów metadanych lub do implementacji specjalnych metod iteracji. BigInt znajduje zastosowanie w obliczeniach finansowych, kryptografii, czy wszędzie tam gdzie potrzebujemy precyzyjnych obliczeń na bardzo dużych liczbach. Należy jednak pamiętać o ograniczeniach - operacje na BigInt są wolniejsze niż na zwykłych liczbach, a Symbole nie mogą być serializowane do JSON.
// Praktyczny przykład użycia Symbol
const STAN_APLIKACJI = Symbol('stan');
const USTAWIENIA = Symbol('ustawienia');
class Aplikacja {
constructor() {
this = {};
this = {};
}
// Te właściwości są "prywatne" i nie pojawią się w JSON
}
// Praktyczny przykład użycia BigInt
const obliczDuzyFactorial = (n) => {
let wynik = 1n;
for (let i = 1n; i <= n; i++) {
wynik *= i;
}
return wynik;
}
console.log(obliczDuzyFactorial(100n)); // Możemy obliczyć factorial ze 100 bez utraty precyzji
Przykłady deklaracji i inicjalizacji
W JavaScript mamy kilka sposobów na deklarowanie i inicjalizację zmiennych. Oprócz podstawowego przypisania wartości, możemy używać destrukturyzacji do wyciągania wartości z obiektów i tablic. Możemy też ustawiać wartości domyślne, które będą użyte gdy przypisana wartość jest undefined.
// Podstawowe deklaracje
let imie = "Jan";
const wiek = 25;
// Destrukturyzacja obiektu
const osoba = { imie: "Anna", wiek: 30, miasto: "Warszawa" };
const { imie: mojeImie, wiek: mojWiek } = osoba;
// Destrukturyzacja tablicy
const kolory = ;
const = kolory;
// Wartości domyślne
const { kraj = "Polska" } = osoba;
const = ;
// Łączenie destrukturyzacji
const config = { ustawienia: { motyw: "ciemny", jezyk: "pl" } };
const { ustawienia: { motyw, jezyk = "en" } } = config;
Zobaczmy teraz, jak różne typy zmiennych są używane w rzeczywistych scenariuszach. Oto przykład prostej aplikacji do zarządzania koszykiem zakupów:
// Stałe konfiguracyjne
const PODATEK_VAT = 0.23;
const MIN_WARTOSC_ZAMOWIENIA = 50;
// Zmienne stanu
let sumaKoszyka = 0;
let liczbaProduktow = 0;
// Dane produktu
const produkt = { id: Symbol('identyfikator-produktu'), nazwa: "Smartfon XYZ", cena: 999.99, dostepny: true };
// Funkcja dodająca do koszyka
function dodajDoKoszyka(produkt, ilosc = 1) {
const { cena } = produkt;
sumaKoszyka += cena * ilosc;
liczbaProduktow += ilosc;
}
// Obliczenia z różnymi typami
const calkowityKoszt = BigInt(Math.floor(sumaKoszyka * (1 + PODATEK_VAT)));
Przy nazywaniu zmiennych i organizacji kodu warto stosować się do pewnych konwencji. Nazwy powinny być opisowe i jasno wskazywać przeznaczenie zmiennej. Stałe często zapisujemy wielkimi literami, a zmienne camelCase. Grupujemy powiązane zmienne blisko siebie.
// Dobre nazewnictwo
const MAX_PROB_LOGOWANIA = 3;
let aktualnaLiczbaProb = 0;
let czyZalogowany = false;
// Grupowanie powiązanych zmiennych
const interfejs = {
przyciskLogowania: document.getElementById('login'),
poleHasla: document.getElementById('haslo'),
komunikatBledu: document.getElementById('blad')
};
// Opisowe nazwy funkcji i parametrów
function obliczCenePoRabacie(cenaBase, procentRabatu, czyPremiumUser = false) {
let finalnaRabat = czyPremiumUser ? procentRabatu + 5 : procentRabatu;
return cenaBase * (1 - finalnaRabat / 100);
}
Podczas debugowania problemów z deklaracjami i inicjalizacją zmiennych, warto zwrócić uwagę na kilka typowych błędów. Oto jak je rozpoznać i naprawić:
// Problem: modyfikacja stałej
const uzytkownik = { imie: "Jan" };
// uzytkownik = {}; // Błąd!
uzytkownik.imie = "Tomek"; // OK - modyfikujemy właściwość
// Problem: użycie zmiennej przed deklaracją
// console.log(zmienna); // undefined lub błąd
let zmienna = 5;
// Problem: zasięg blokowy
if (true) {
let zmiennaBlokowa = 10;
}
// console.log(zmiennaBlokowa); // Błąd!
// Rozwiązanie: przenieś deklarację
let zmiennaBlokowa;
if (true) {
zmiennaBlokowa = 10;
}
console.log(zmiennaBlokowa); // OKOperatory arytmetyczne (+, -, \*, /)
W JavaScript, podobnie jak w matematyce, możemy wykonywać podstawowe operacje arytmetyczne. Zacznijmy od najprostszych działań, które wykonujemy na co dzień.
Do dyspozycji mamy operatory dodawania (+), odejmowania (-), mnożenia (\*) i dzielenia (/). Spójrzmy na przykład:
let cena = 100;
let podatekVAT = 0.23;
let cenaKoncowa = cena + (cena \* podatekVAT);
console.log(cenaKoncowa); // Wyświetli: 123
JavaScript przestrzega standardowych reguł kolejności działań matematycznych - najpierw wykonywane są działania w nawiasach, później mnożenie i dzielenie, a na końcu dodawanie i odejmowanie. Jeśli nie jesteś pewien kolejności, zawsze używaj nawiasów - lepiej napisać więcej nawiasów niż otrzymać błędny wynik!
Bardzo przydatnym operatorem jest modulo (%), który zwraca resztę z dzielenia. Przyda się na przykład do sprawdzenia, czy liczba jest parzysta:
let liczba = 15;
let czyParzysta = liczba % 2; // Jeśli wynik to 0, liczba jest parzysta
console.log(czyParzysta); // Wyświetli: 1 (bo 15 jest nieparzyste)
Do potęgowania służy operator \*\*. Jest to wygodniejszy sposób niż używanie funkcji Math.pow():
let podstawa = 2;
let wykladnik = 3;
let wynik = podstawa ** wykladnik;
console.log(wynik); // Wyświetli: 8 (bo 2 * 2 * 2 = 8)
Przejdźmy teraz do operatorów inkrementacji (++) i dekrementacji (--). Są to bardzo przydatne operatory, które zwiększają lub zmniejszają wartość o 1. Możemy je zapisać przed zmienną (preinkrementacja) lub po zmiennej (postinkrementacja):
let licznik = 5;
console.log(++licznik); // Wyświetli: 6 (najpierw zwiększa, potem zwraca)
console.log(licznik++); // Wyświetli: 6 (najpierw zwraca, potem zwiększa)
console.log(licznik); // Wyświetli: 7
JavaScript ma kilka specjalnych wartości numerycznych. Kiedy dzielimy przez zero, otrzymujemy Infinity lub -Infinity:
let nieskonczonosc = 1 / 0;
console.log(nieskonczonosc); // Wyświetli: Infinity
let ujemnaNieskonczonosc = -1 / 0;
console.log(ujemnaNieskonczonosc); // Wyświetli: -Infinity
Czasami możemy otrzymać wartość NaN (Not a Number). Dzieje się tak, gdy próbujemy wykonać nieprawidłowe działanie matematyczne:
let dziwnyWynik = 0 / 0;
console.log(dziwnyWynik); // Wyświetli: NaN
// Sprawdzanie czy wynik to NaN
console.log(isNaN(dziwnyWynik)); // Wyświetli: true
Na koniec musimy porozmawiać o typowych pułapkach w obliczeniach. JavaScript używa 64-bitowej precyzji liczb zmiennoprzecinkowych, co może prowadzić do dziwnych wyników:
let dziesietne = 0.1 + 0.2;
console.log(dziesietne); // Wyświetli: 0.30000000000000004
Aby rozwiązać ten problem, możemy zaokrąglić wynik do określonej liczby miejsc po przecinku:
let poprawnyWynik = (0.1 + 0.2).toFixed(1);
console.log(poprawnyWynik); // Wyświetli: "0.3"
// Jeśli potrzebujemy liczby, a nie stringa:
let liczbaPoprawna = Number((0.1 + 0.2).toFixed(1));
console.log(liczbaPoprawna); // Wyświetli: 0.3
Operatory porównania (==, ===, !=, !==)
Zacznijmy od jednej z najważniejszych rzeczy w JavaScript - różnicy między operatorami == (porównanie luźne) a === (porównanie ścisłe). To źródło wielu błędów wśród początkujących programistów!
Operator == przed porównaniem próbuje przekonwertować wartości do tego samego typu. Zobaczmy to na przykładzie:
console.log(5 == "5"); // true - string "5" zostaje przekonwertowany na liczbę 5
console.log(0 == false); // true - false zostaje przekonwertowany na 0
console.log("" == false); // true - pusty string i false są konwertowane na 0
console.log(null == undefined); // true - specjalny przypadek w JavaScript
Z kolei operator === (porównanie ścisłe) sprawdza zarówno wartość jak i typ danych. Nie wykonuje żadnych konwersji:
console.log(5 === "5"); // false - różne typy (number vs string)
console.log(0 === false); // false - różne typy (number vs boolean)
console.log("" === false); // false - różne typy (string vs boolean)
console.log(null === undefined); // false - różne typy
Przy porównywaniu różnych typów danych można wpaść w sporo pułapek. Oto najbardziej podchwytliwe przypadki:
// Pułapka 1: Tablice
console.log( == false); // true - pusta tablica konwertuje się do 0
console.log( == "1,2"); // true - tablica konwertuje się do stringa!
// Pułapka 2: Obiekty
console.log({} == {}); // false - porównywane są referencje, nie zawartość
console.log({} == ""); // true - obiekt konwertuje się do stringa
// Pułapka 3: null i undefined
console.log(null == 0); // false - specjalny przypadek
console.log(undefined == 0); // false - specjalny przypadek
Najlepsza praktyka? Zawsze używaj === zamiast ==. Dzięki temu unikniesz nieoczekiwanych konwersji typów i twój kod będzie bardziej przewidywalny.
Przejdźmy teraz do operatorów większości i mniejszości. JavaScript umożliwia porównywanie nie tylko liczb, ale także stringów i dat:
// Porównywanie liczb
console.log(5 > 3); // true
console.log(5 >= 5); // true
console.log(3 < 5); // true
console.log(3 <= 3); // true
// Porównywanie stringów (według kolejności alfabetycznej)
console.log("kot" > "koc"); // true - 't' jest później w alfabecie niż 'c'
console.log("Alice" < "Bob"); // true - 'A' jest wcześniej niż 'B'
console.log("100" > "99"); // false - uwaga! To porównanie tekstowe!
// Porównywanie dat
let wczoraj = new Date(2024, 11, 5);
let dzisiaj = new Date(2024, 11, 6);
console.log(dzisiaj > wczoraj); // true
console.log(wczoraj < dzisiaj); // true
W przypadku stringów porównywanie odbywa się znak po znaku, zgodnie z ich kodem Unicode. Wielkie litery są "mniejsze" niż małe, więc "Z" < "a" zwróci true. Przy porównywaniu dat JavaScript automatycznie konwertuje je na timestamp (liczba milisekund od 1 stycznia 1970), dzięki czemu porównania działają poprawnie.
Operatory logiczne (&&, ||, !)
Operatory logiczne w JavaScript są podstawowym narzędziem do tworzenia warunków i kontrolowania przepływu programu. Zacznijmy od operatora AND (&&), OR (||) i NOT (!). W najprostszym przypadku działają one na wartościach logicznych (true/false):
let prawda = true;
let falsz = false;
console.log(prawda && falsz); // false (true AND false)
console.log(prawda || falsz); // true (true OR false)
console.log(!prawda); // false (NOT true)
JavaScript wykorzystuje mechanizm zwany "short-circuit evaluation" (ocena w skróconym obwodzie). Oznacza to, że jeśli pierwszy operand determinuje wynik, drugi nie jest w ogóle sprawdzany:
// Dla && (AND): jeśli pierwszy operand jest false, drugi nie jest sprawdzany
console.log(false && kosztownaOperacja()); // false, kosztownaOperacja() nie zostanie wywołana
// Dla || (OR): jeśli pierwszy operand jest true, drugi nie jest sprawdzany
console.log(true || kosztownaOperacja()); // true, kosztownaOperacja() nie zostanie wywołana
Ta właściwość jest niezwykle przydatna w praktyce. Możemy jej użyć do sprawdzania istnienia wartości i ustawiania wartości domyślnych:
// Sprawdzanie czy obiekt i jego właściwość istnieją
let uzytkownik = { imie: "Jan", adres: { miasto: "Warszawa" } };
// Bezpieczne sprawdzenie zagnieżdżonej właściwości
let miasto = uzytkownik && uzytkownik.adres && uzytkownik.adres.miasto;
// Ustawianie wartości domyślnej
let imie = uzytkownik.imie || "Gość";
let wiek = uzytkownik.wiek || 18; // jeśli wiek nie istnieje, użyj 18
JavaScript wprowadził także operator nullish coalescing (??), który działa podobnie do ||, ale traktuje wartości inaczej. Operator ?? zwraca prawą wartość tylko wtedy, gdy lewa jest null lub undefined:
// Różnica między || a ??
let liczba = 0;
console.log(liczba || "domyślna"); // "domyślna" (bo 0 jest falsy)
console.log(liczba ?? "domyślna"); // 0 (bo 0 nie jest null ani undefined)
let pustyString = "";
console.log(pustyString || "domyślna"); // "domyślna" (bo "" jest falsy)
console.log(pustyString ?? "domyślna"); // "" (bo "" nie jest null ani undefined)
let nieistniejaca;
console.log(nieistniejaca ?? "domyślna"); // "domyślna" (bo undefined)
let nullowa = null;
console.log(nullowa ?? "domyślna"); // "domyślna" (bo null)
Operator ?? jest szczególnie przydatny gdy chcemy zachować wartości "falsy" (jak 0 czy pusty string), które są prawidłowymi danymi w naszej aplikacji:
function wyswietlWynik(wynik) {
// Użycie || mogłoby być błędne dla wyniku 0
const komunikat = wynik ?? "Brak wyniku";
console.log(komunikat);
}
wyswietlWynik(0); // wyświetli: 0
wyswietlWynik(""); // wyświetli: ""
wyswietlWynik(null); // wyświetli: "Brak wyniku"