JS cz.3

Podstawowe optymalizacje pętli:

-wyciągnąć element/wartość przed for (np. tablica.lenght) – żeby nie procesować tej danej

-specjalne typy pętli/ specjalne konstrukcje

dla przypomnienia:

PĘTLA FOR — pętla „po liczbach / krokach”

Co iteruje?

licznik, czyli liczby: 0, 1, 2, 3…

Po co?

Gdy potrzebujesz kontroli:

  • zacząć od konkretnej liczby
  • skakać co 1, co 2, co 5
  • zatrzymać się w dowolnym momencie
  • wykonać pętlę np. 100 razy
for (let i = 0; i < 5; i++) {
  console.log("i =", i);
}
const owoce = ["jabłko", "banan", "gruszka"];

for (let i = 0; i < owoce.length; i++) {
  console.log(i, owoce[i]);
  }

CZYLI: „Dla zmiennej 'i', zaczynając od 0, dopóki i jest mniejsze niż 5, w każdej iteracji(w każdym kolejnym kroku) zwiększaj 'i' o 1 i wykonaj blok kodu{}.”


FOR...OF — pętla „po wartościach tablicy”

Co iteruje?

wartości tablicy, np.:
"jabłko", "banan", "gruszka"

Po co?

➡Gdy chcesz po prostu przejść przez elementy tablicy i nic więcej.
➡ Bez indeksów, bez kombinacji.

Pętla for...of w JavaScript to specjalna odmiana pętli do iterowania po wartościach tablic, stringów i innych obiektów iterowalnych (np. Map, Set).

for...of iteruje po wartościach tablicy według indeksów od 0 do length-1. (brakujące indeksy = undefined, właściwości kluczy obiektów będą pomijane)

  • Przechodzi po każdej wartości kolekcji
  • Nie wymaga licznika ani indeksu (choć indeks można uzyskać przez .entries())
  • Czytelniejsza niż klasyczny for w przypadku prostego iterowania po tablicy
for (const element of kolekcja) {
  // kod do wykonania dla każdego elementu
}

element – zmienna, która przy każdej iteracji przyjmuje wartość kolejnego elementu tablicy/iterowalnego obiektu

const fruits = ["jabłko", "banan", "gruszka"];

for (const fruit of fruits) {
  console.log(fruit);
}
// wypisze jabłko, banan, gruszka

CZYLI: „Dla każdego owocu w tablicy owoców, pokaż w konsoli jego nazwę.”

inaczej: Dla każdej zmiennej x/elementu tablicy zmiennej y zrób to: blok kodu

Co się dzieje:

  1. const fruits = ["jabłko", "banan", "gruszka"];
    • Tworzymy tablicę fruits zawierającą trzy elementy: "jabłko", "banan" i "gruszka"
  2. for (const fruit of fruits)
    • Pętla mówi: „weź każdy element z tablicy fruits kolejno i przypisz go do zmiennej fruit
    • const fruit = zmienna tymczasowa, która w każdej iteracji przechowuje aktualny owoc
  3. console.log(fruit);
    • Wypisuje w konsoli wartość zmiennej fruit
    • W pierwszej iteracji: "jabłko"
    • W drugiej iteracji: "banan"
    • W trzeciej iteracji: "gruszka"
  4. Po ostatnim elemencie pętla kończy działanie

for...of po STRINGU

for...of przechodzi po każdej literze w stringu.

const fruits = ["jabłko", "banan"];

for (const fruit of fruits) {
  console.log("Owoc:", fruit);

  for (const letter of fruit) {
    console.log(" - litera:", letter);
  }
}

Pętla for...of iteruje po tym, co jest po prawej stronie of.
Jeśli tam jest tablica → dostajesz elementy tablicy.
Jeśli tam jest string → dostajesz litery string

for (const fruit of fruits)
Ta linijka tworzy zmienną fruit - deklaruje ją, w tym przypadku mamy 3 zmienne.fruit nie jest częścią tablicy fruits. Sami tworzymy zmienną fruit w pętli. A for...of po prostu wstawia do niej kolejne elementy tablicy.


PĘTLA FOR IN — pętla „po kluczach (nazwach pól)”

Co iteruje?

klucze obiektu
(bo tablica też jest obiektem, tylko kluczami są indeksy - które zawsze są STRINGAMI, choć JS może automatycznie konwertować je na liczby(?))

Dla tablicy: "0", "1", "2", …"

Dla obiektu:

{ imie: "Ala", wiek: 25 }

klucze = "imie", "wiek"

Po co?

➡Do iterowania po obiektach, nie po tablicach.
➡Gdy chcesz poznać nazwy pól i ich wartości.

to iteracja po indexach - iteruje po KLUCZACH nie po wartościach (kluczem może być numer indexu, któremu przyporządkowana jest wartość lub string, któremu przyporządkowana jest wartość np. imię:Anna)

 console.log("For...in (indeksy jako stringi):");
        for (const indeks in owoce) {
            console.log("Indeks:", indeks, "Typ:", typeof indeks);
            console.log("Wartość:", owoce[indeks]);
        }

CZYLI: „Dla każdego klucza w obiekcie/ tablicy wykonaj kod znajdujący się w pętli.”

Pętla / Metoda Co iteruje? Co wypisuje w console.log? Uwagi / typowe zastosowanie
Klasyczny for Liczbę od 0 do length-1 Wartości tablicy: arr[i] Wymaga ręcznej kontroli indeksu. Można użyć i do obliczeń.
for...of Wartości tablicy Elementy tablicy bezpośrednio Najprostszy sposób iterowania po wartościach. Nie iteruje po dodatkowych właściwościach/tablicy.
for...in Klucze obiektu (indeksy w tablicy) Klucze tablicy jako stringi ("0", "1"…) Działa po wszystkich kluczach obiektu/tablicy, w tym dodatkowych własnościach. Często nie zaleca się dla tablic, bo może iterować po metodach prototypu.
Pętla Iteruje po Idealna do Przykład
for liczby (indeksy) precyzyjna kontrola liczenie od 0 do n
for...of wartości tablicy przeglądanie elementów owoce, lista zakupów
for...in klucze obiektu obiekty (pola) osoba.imie, osoba.wiek

JS_7.3 - informacje dotyczące optymalizacji - do przeanalizowania


ITERATOR -to obiekt, który pozwala „czytać” elementy kolekcji po jednym.

Iterować (od łac. iterare – „powtarzać”) znaczy powtarzać jakąś czynność dla kolejnych elementów  (np. tablicy, zbioru Set, mapy Map, łańcucha znaków itp.). — czyli przechodzić po każdym elemencie po kolei i coś z nim robić

Iterator „pamięta”, na którym elemencie aktualnie się znajduje i ma metodę next(), która zwraca kolejny element - coś jak „czytnik”, który przesuwa się dalej i czyta kolejne elementy, pytając czy to już ostatni?

Każda tablica ma wbudowany iterator.

Teraz możemy go „ręcznie” przesuwać:

Każde wywołanie .next() daje następny element aż do końca.

Iterator może być ukryty np. pod przyciskiem w HTMLu

Duży skrót: do czego służy next()?

do ręcznego iterowania po tablicy lub stringu

do budowania własnych pętli

do głębokiego zrozumienia mechanizmu pętli for...of
(bo for of działa wewnętrznie dokładnie tak jak next(), tylko automatycznie)

const fruits = ["jabłko", "banan"];
const iterator = fruits[Symbol.iterator]();

console.log(iterator.next()); // { value: "jabłko", done: false }
console.log(iterator.next()); // { value: "banan", done: false }
console.log(iterator.next()); // { value: undefined, done: true }

Za KAŻDYM razem, gdy wywołasz next(), dostajesz obiekt:

{ value: ..., done: ... }

  • value → kolejny element
  • done → informacja czy iteracja się skończyła
    • false → nadal są elementy
    • true → koniec iteracji

Najpierw trzeba sprawdzić czy można używać tej metody na danym typie danych

- czy obiekt jest iterable

function czyIterable(obj) {
            return obj != null && typeof obj[Symbol.iterator] === "function";
        }
        
        console.log("[] jest iterable:", czyIterable([])); // true
        console.log("{} jest iterable:", czyIterable({})); // false
        console.log("'tekst' jest iterable:", czyIterable("tekst")); // true

w JavaScript mamy różne sposoby definiowania funkcji, a od tego zależy m.in. hoisting i sposób użycia zmiennej

  1. Function Declaration (deklaracja funkcji)

Funkcje deklaruje się, a potem wywołuje, jest ona hoistowana, więc można ją najpierw wywołać a potem zadeklarować

sayHi(); // działa!
function sayHi() { console.log("Hi");
  1. Function Expression (wyrażenie funkcyjne)
const greet = function() {
  console.log("Cześć!");
};

Tutaj funkcja jest przypisana do zmiennej. Nie ma hoistingu jak deklaracja — nie możesz wywołać greet() przed tą linią, bo zmienna jeszcze nie istnieje.

  1. Arrow Function (funkcja strzałkowa)
const greet = () => {
  console.log("Cześć!");
};

To też function expression, więc nie jest hoistowana. Nie ma własnego this ani arguments — zachowuje się inaczej niż zwykła funkcja

Pojęcie

Co to jest

Gdzie występuje

Parametr

Nazwa zmiennej, którą deklarujesz w definicji funkcji, scope blokowy

W nagłówku funkcji

Argument

Wartość, którą przekazujesz do funkcji przy jej wywołaniu

W momencie wywołania

function greet(name) {
  console.log("Cześć, " + name);
}

// wywołanie funkcji z argumentem "Ala"
greet("Ala"); 

Do funkcji możemy podac dowolne argumenty – to JS obsługuje jako pseudo tablica – to obiekt jak tablica, ale nie ma on metod tablicowych (nie obsługuje narzędzi typowych dla tablic). Jeżeli chcemy otrzymać listę argumentów przekazanych do funkcji to możemy to zrobić w ten sposób:

// Obiekt arguments
        function sumujWszystkie() {
            console.log("Arguments:", arguments);
            console.log("Typ:", typeof arguments);
            console.log("Length:", arguments.length);
            const argumentsArray = Array.from(arguments); // konwersja na tablicę
            console.log("Po konwersji:", Array.isArray(argumentsArray)); // true
            console.log("Tablica:", argumentsArray);
            let suma = 0;
            for (let i = 0; i < arguments.length; i++) {
                suma += arguments[i];
            }
            return suma;
        }


        const suma2 = sumujWszystkie(1, 2, 3, 4, 5, 6);
        console.log("Suma wszystkich:", suma2); // 21

Jeżeli chcemy sprawdzić czy zmienna jest tablicą:

const fruits = ["jabłko", "banan"];
const notArray = { a: 1 };

console.log(Array.isArray(fruits));   // true
console.log(Array.isArray(notArray)); // false

Zmienna blokowa – wewnątrz funkcji, zmienna globalna – poza funkcją

Scope chaining – JS sprawdza czy zmienna nie została zadklarowana, szuka wyżej - to co jest poza pętlą, poza blokiem nie będzie działało


Kluczowe punkty shadowingu:

  1. Dotyczy nazw zmiennych (var, let, const) oraz parametrów funkcji.
  2. Zmienne w wewnętrznym bloku mają pierwszeństwo przed zmiennymi o tej samej nazwie w zewnętrznym bloku.

Może prowadzić do błędów i trudnych do znalezienia bugów, jeśli nie jesteśmy świadomi, że nazwy się powtarzają

CZYLI - Globalna x istnieje cały czas i ma wartość 10. Jeśli w funkcji tworzymy lokalną x (shadowing) to ta lokalna x „przykrywa” globalną tylko wewnątrz funkcji. Wszystkie odwołania do x w funkcji używają lokalnej zmiennej, nie globalnej. Po wyjściu z funkcji: Lokalna x znika, a globalna x pozostaje nietknięta.


NAZYWANIE FUNKCJI

-CamelCase jest też stosowany do funkcji

-funkcja powinna komunikować co robi
- funkcje zwykle wykonują akcje wiec ich nazwy powinny zawierać czasownik
Is, can, has – początek, długość nazwy proporcjonalna do zakresu – lokalne krótsze, opisowe to te używane w wielu miejscach


ARROW FUNCTION

Funkcja strzałkowa (arrow function) to krótszy zapis funkcji w JavaScript, wprowadzony w ES6. Ma kilka cech, które odróżniają ją od klasycznej funkcji (function).

const greet = () => {
  console.log("Cześć!");
}

greet(); // "Cześć!"
  • Strzałka => zastępuje słowo function.
  • Funkcję przypisuje się do zmiennej =
  • Jeśli ciało funkcji ma tylko jedno wyrażenie, można pominąć {} i return.
const greet = () => console.log("Cześć!");

Funkcje arrow są anonimowe, można je przypisać do zmiennej, nie mają własnej nazwy co ma znaczenie przy debugowaniu kodu i używaniu rekurencji. Rekurencja w programowaniu to technika, w której funkcja wywołuje samą siebie, żeby rozwiązać problem.

Arrow Functions mogą być wieloliniowe albo być w ciele funkcji, możemy je łączyć z innymi funkcjami

Różnice między arrow function a zwykłą funkcją:

CechaZwykła funkcjaArrow function
thisdynamicznedziedziczone z otaczającego kontekstu
argumentsdostępnebrak (można użyć rest ...args)
Hoistingtaknie (jak function expression)
Krótsza składnianietak

Default parameters (parametry domyślne)

Pozwalają ustawić wartość domyślną dla parametru funkcji, jeśli argument nie zostanie przekazany lub jest undefined.

function greet(name = "Gość") {
  console.log("Cześć, " + name);
}

greet("Ala"); // Cześć, Ala
greet();      // Cześć, Gość

UWAGA: null nie aktywuje wartości domyślnej, bo JS traktuje null jako prawidłową wartość:

*ciekawostka

W JS prompt() może przyjmować drugi argument – to jest wartość domyślna, która pojawia się w polu input:

const name = prompt("Jak masz na imię?", "Ala");
console.log(name);
  • Jeśli użytkownik nie wpisze nic i kliknie OK, wartość będzie "" (pusty string).
  • Jeśli użytkownik kliknie Anuluj, wynik będzie null.

Rest parameters (parametry reszty)

  • Pozwalają zebrać dowolną liczbę argumentów w jedną tablicę.
  • Zapisuje się je jako ...nazwa.
function showColors(...colors) {
  console.log("Kolory, które podałeś:", colors);
}

showColors("czerwony", "zielony", "niebieski");
CechaDefault parameterRest parameter
Celustawienie wartości, jeśli brak argumentuzebranie wszystkich nadmiarowych argumentów w tablicę
Składniaparam = wartość...param
Liczba argumentówpojedynczy parametrdowolna liczba

Higher-order function (funkcja wyższego rzędu)

W JavaScript funkcja wyższego rzędu to funkcja, która:

Przyjmuje inną funkcję jako argument, lub zwraca funkcję jako wynik.

Innymi słowy – funkcja, która operuje na innych funkcjach.

function greetUser(name, upperCaseName) {
  console.log("Cześć, " + upperCaseName(name));
}

function toUpperCase(name) {
  return name.toUpperCase();
}

greetUser("Ala", toUpperCase); // Cześć, ALA

First-class citizens (obywatele pierwszej klasy)

  • W JS funkcje są first-class citizens, czyli pełnoprawnymi obiektami.
  • Co to oznacza w praktyce:
    • Można je przypisać do zmiennej
    • Można je przekazywać jako argumenty do innych funkcji
    • Można je zwracać z funkcji
    • Mogą mieć własne właściwości

Callback pattern – funkcje jako argumenty

  • Funkcja przekazana do innej funkcji jako argument, która zostanie wywołana w określonym momencie.
  • Pozwala programowi wywołać kod “po czymś”, np. po zakończeniu operacji asynchronicznej.

Zwracanie funkcji z funkcji – tworzenie closures (domknięć)

  • Funkcja może zwracać inną funkcję.
  • Funkcja wewnętrzna “pamięta” zmienne z otaczającej funkcji – to właśnie closure.
function greet(name) {
  return function() {
    console.log("Cześć, " + name + "!");
  };
}

const greetAla = greet("Ala");
greetAla(); // Cześć, Ala!

W tym przypadku przypisanie funkcji greetAla do nowej zmiennej pozwoli przywoływać ją wielokrotnie = bo funkcja zwracana przez funkcję to nowa funkcja. Każde wywołanie greet("Ala") tworzy nową funkcję, która “pamięta” name.