JavaScript 12.11
Pętle zaawansowane
Break Continue Po co przerywać wcześniej pętle? - Wydajność pętli, nie procesujemy kodu który jest zbędny. Zagnieżdżone pętle zwiększają obliczenia.
Złożoność czasowa/obliczeniowa ~n*(n-1)/2 -> O(n2) //dwa tzn do kwadratu// - koszt tej operacji rośnie w kwadracie do rozmiaru danych, wszystkie nasze break i contiune maja za zadanie zoptymalizować wydajność pętli, jeśli dostajemy już wynik który spełnia warunek to nie ma potrzeby wykonywać kolejnych iteracji.
Można np. wyjąć zmienną typu długość tablicy, jak wyrzucimy z pętli to nie będzie się to tam za każdym razem przeliczać
JS ma kilka typów pętli które pozwalają iterować się po specjalnych konstruktach. Np. obiekt chcemy się po nim poruszać, wyświetlić jego wszystkie elementy, to zechcemy skorzystać z wbudowanej pętli aby nie tworzyć własnych konstruktów, nie obliczać długość tablicy czy innych tego typu wyliczeń
- For.. of iteracja po wartościach tablicy dla każdego elementu tablicy zrób to -> ten blok kodu. Nie trzeba tworzyć w takiej pętli iteratora. nie możemy sięgnąć do indeksu, jeśli chcemy porównywać wartości (jak wartość jest taka to zmień ją na inną) to bez dodania osobnej zmiennej rejestrującej indeks nie jesteśmy w stanie tego zrobić
- For... in iteracja po kluczach/indeksach, for, definicja indeksu w tablicy i dostajemy ten indeks jako nr nie jako element np. banan tylko 150 (jego indeks), możemy sprawdzić jakiego typu jest ten indeks, możemy sięgnąć do tego elementu tablicy, to przydaje się w sytuacji kiedy mamy tablice z wieloma elementami które są różne. Nie da sie wyświetlić obiektu - wartosci
- For... of ze stringiem -możemy iterowac po stringu - każda litera jest wyrzucana osobno, można się iterowac przez elementy string, to się przydaje np do napisania gry wisielca, zakrywanie literek, jak zgadnie literkę to wysświetlamy ją. Musimy wiedzieć ile tych liter jest, jakie one są i np. taka pętla możemy zrobić takie coś
- For.. in z obiektem (właściwe użycie) cont osoba to jesteśmy w stanie takim forem przeiterowac się przez obiekt i dostać wartości
For of nie działa z prostymi obiektami, aby zadziałało musimy sięgnąć do właściwości obiektu żeby sobie z niego przeczytać to co potrzebujemy, możemy przeczytać po kluczach ale nie po wartościach
- For .. of Map - mapowanie wartości, możemy dzięki temu poruszać się po takich wartościach
- For of z Set - dla zbiorów, łatwiej po obiektach czy po tablicy obiektów raczej nie będziemy używać
- sumowanie - nie ma iteratora, nie ma definicji warunku, poruszamy się po wszystkich wartościach jakie ma ta tablica
// For...of - iteracja po WARTOŚCIACH tablicy
const owoce = ["jabłko", "banan", "pomarańcza", 1, true, null, {name:"kiwi"}];
for(i=0; i<owoce.length; i++) {
console.log(owoce[i]);
}
console.log("For...of (wartości):");
for (const owoc of owoce) {
console.log(owoc);
}
// For...in - iteracja po KLUCZACH/INDEKSACH
console.log("For...in (indeksy jako stringi):");
for (const indeks in owoce) {
console.log("Indeks:", indeks, "Typ:", typeof indeks);
console.log("Wartość:", owoce[indeks]);
}
// For...of ze stringiem
const tekst = "JavaScript";
console.log("For...of przez string:");
for (const litera of tekst) {
console.log(litera);
}
// For...in z obiektem (właściwe użycie)
const osoba = {
imie: "Jan",
wiek: 30,
miasto: "Warszawa"
};
console.log("For...in przez obiekt:");
for (const klucz in osoba) {
console.log(klucz + ":", osoba[klucz]);
}
// For...of NIE DZIAŁA z prostymi obiektami
// for (const value of osoba) { // BŁĄD!
// console.log(value);
// }
// Alternatywy dla obiektów:
console.log("Object.keys():");
for (const klucz of Object.keys(osoba)) {
console.log(klucz + ":", osoba[klucz]);
}
console.log("Object.values():");
for (const wartosc of Object.values(osoba)) {
console.log(wartosc);
}
console.log("Object.entries():");
for (const [klucz, wartosc] of Object.entries(osoba)) {
console.log(klucz + ":", wartosc);
}
// For...of z Map
const mapa = new Map([
["PL", "Polska"],
["DE", "Niemcy"],
["FR", "Francja"]
]);
console.log("For...of przez Map:");
for (const [kod, kraj] of mapa) {
console.log(kod + " =", kraj);
}
// For...of z Set
const zbior = new Set([1, 2, 3, 4, 5]);
console.log("For...of przez Set:");
for (const liczba of zbior) {
console.log(liczba);
}
// Praktyczny przykład - sumowanie
const liczby = [10, 20, 30, 40, 50];
let suma = 0;
for (const liczba of liczby) {
suma += liczba; // suma = suma + liczba
}
console.log("Suma:", suma);
// Wyświetlenie
let wynik = "";
for (const owoc of owoce) {
wynik += owoc + "<br>";
}
document.getElementById("forInOf").innerHTML =
"Owoce (for...of):<br>" + wynik +
"<br>Suma liczb: " + suma;jest możliwe tworzenie pętli w taki sposób, żeby sięgać bezpośrednio do czegoś bez pętli for. jedna z podstawowych technik to cashowanie długości tablicy, wyciągnijmy go wcześniej, jak tablica się nie zmienia to iterujemy się do stałej wartości. jeśli się zmienia to taka iteracje powinniśmy zamknąć w funkcji i wywoływać taka funkcje za każdym razem kiedy robimy zmianę w tablicy.
jeśli nie można cash tablicy to można odwrócić tablice - odwrotna iteracja może być szybsza, bo skoro idziemy od długości do zera to porównanie do zera jest szybsze niż porównywanie cały czas długości tablicy.
Lazy evalutaion - jeśli nie trzeba robić jakich obliczeń nie wykonywać ich aż nie są potrzebne. zamkniecie ich w if, w instrukcji warunkowej, stworzenie flagi i inst. warunkowej która zrobi break by nie kręciła się bezsensownie dalej. Nie myśleć o optymalizacji na początku, napisać kod, zobaczyć czy jest czytelny potem dopiero robić optymalizacje
OPTYMALIZACJA
// 1. Cachowanie długości
const duzaTablica = Array.from({ length: 10000 }, (_, i) => i);
// Bez cachowania (gorsze)
let suma1 = 0;
for (let i = 0; i < duzaTablica.length; i++) {
suma1 += duzaTablica[i];
}
// Z cachowaniem (lepsze)
let suma2 = 0;
const len = duzaTablica.length;
for (let i = 0; i < len; i++) {
suma2 += duzaTablica[i];
}
console.log("Suma:", suma2);
// 2. Minimalizowanie dostępu do właściwości
const obiekt = {
klucz: "wartość",
dane: {
wartosci: [1, 2, 3, 4, 5]
}
}; // Przykład zagnieżdżonego obiektu obiekt.dane.wartosci[]
// Wolniejsze - wielokrotny dostęp
let suma3 = 0;
for (let i = 0; i < obiekt.dane.wartosci.length; i++) {
suma3 += obiekt.dane.wartosci[i];
}
// Szybsze - cachowanie odniesienia
const wartosci = obiekt.dane.wartosci; // tablica
const dlugosc = wartosci.length; // liczba elementów
let suma4 = 0;
for (let i = 0; i < dlugosc; i++) {
suma4 += wartosci[i];
}
console.log("Suma z cachowaniem:", suma4);
// 3. Odwrotna iteracja
const liczby = [1, 2, 3, 4, 5];
let suma5 = 0;
// Od końca do początku
for (let i = liczby.length - 1; i >= 0; i--) {
suma5 += liczby[i];
}
console.log("Suma (odwrotnie):", suma5);
// 4. Użycie Map zamiast zagnieżdżonych pętli
const produkty = [
{ id: 1, nazwa: "A" },
{ id: 2, nazwa: "B" },
{ id: 3, nazwa: "C" }
];
const zamowienia = [
{ produktId: 2, ilosc: 5 },
{ produktId: 1, ilosc: 3 }
];
// WOLNE - zagnieżdżone pętle O(n*m)
const wyniki1 = [];
for (const zamowienie of zamowienia) {
for (const produkt of produkty) {
if (produkt.id === zamowienie.produktId) {
wyniki1.push({
nazwa: produkt.nazwa,
ilosc: zamowienie.ilosc
});
}
}
}
// SZYBKIE - Map O(n+m)
const produktyMap = new Map();
for (const produkt of produkty) {
produktyMap.set(produkt.id, produkt);
}
const wyniki2 = [];
for (const zamowienie of zamowienia) {
const produkt = produktyMap.get(zamowienie.produktId);
if (produkt) {
wyniki2.push({
nazwa: produkt.nazwa,
ilosc: zamowienie.ilosc
});
}
}
console.log("Wyniki z Map:", wyniki2);
// 5. Lazy evaluation - nie obliczaj niepotrzebnie
const dane = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// GORSZE - oblicza wszystko
const kwadratyWszystkie = [];
for (const num of dane) {
kwadratyWszystkie.push(num * num);
}
const pierwsze3 = kwadratyWszystkie.slice(0, 3);
// LEPSZE - oblicza tylko potrzebne
const kwadraty3 = [];
for (let i = 0; i < Math.min(3, dane.length); i++) {
kwadraty3.push(dane[i] * dane[i]);
}
console.log("Pierwsze 3 kwadraty:", kwadraty3);
// 6. Wczesne wyjście
function znajdzParzysta(tablica) {
for (let i = 0; i < tablica.length; i++) {
if (tablica[i] % 2 === 0) {
return tablica[i]; // Natychmiast kończy
}
}
return null;
}
const parzysta = znajdzParzysta([1, 3, 5, 8, 10]);
console.log("Pierwsza parzysta:", parzysta);
// 7. Unikanie tworzenia obiektów w pętli
// WOLNIEJSZE
const wyniki3 = [];
for (let i = 0; i < 1000; i++) {
wyniki3.push({ indeks: i, wartosc: i * 2 });
}
// SZYBSZE dla prostych przypadków
const indeksy = [];
const wartosci2 = [];
for (let i = 0; i < 1000; i++) {
indeksy.push(i);
wartosci2.push(i * 2);
}
// 8. Batch processing dla dużych danych
function przetworzBatch(dane, rozmiarBatcha) {
const wyniki = [];
for (let i = 0; i < dane.length; i += rozmiarBatcha) {
const batch = dane.slice(i, i + rozmiarBatcha);
// Przetwarzaj batch
for (const element of batch) {
wyniki.push(element * 2);
}
}
return wyniki;
}
const duzeDane = Array.from({ length: 100 }, (_, i) => i);
const przetworzone = przetworzBatch(duzeDane, 10);
// Wyświetlenie
document.getElementById("optimization").innerHTML =
"Suma z cachowaniem: " + suma2 + "<br>" +
"Wyniki z Map: " + wyniki2.length + " elementów<br>" +
"Pierwsza parzysta: " + parzysta;Iteratory
Metoda next - pozwalają na odczytywanie bieżącego elementu i posuwanie się po elementach wartościach next next next - zwraca obiekt wartość - bieżąca wartość i druga własciwosc która jest done - czyli sprawdzamy czy obiket sie skonczył się w tym miejscu obiekt, dla 1 2 3 nie skończył sie done:false ale dla ostatniej done:true i pokazuje undefined BO WARTOSCI NIE MA wyskoczyliśmy poza tablice.
Możemy w ręczny sposób posługiwać się iteracjami, nie musi być to pętla, ale możemy poruszać się pomiędzy wartościami. To przypomina paging na str, np. duże tabele, dużo wartości alledrogo jak wyświetla ileś produktów to często jest tych produktów tak dużo ze na końcu tej listy jest next - kolejne produkty, pokaz kolejne itp. dokładnie tak samo działa taki iterator.
Czy możemy użyć na tym typie danych tych konstruktów- sprawdzanie czy obiekt jest iterable
// Podstawowe użycie iteratora
const tablica = [1, 2, 3];
const iterator = tablica[Symbol.iterator]();
console.log("Ręczne używanie iteratora:");
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
// tablica[i] <-- i=i-1; | i=i+1; -->
// Sprawdzanie 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")); // truePodstawy funkcji
Funkcja nie musi mieć argumentów, może cos przyjmowac np.
Hoisting - cecha w JS, która powoduje, że deklaracja funkcji mimo ze jest niżej, bierze ten fragment kodu i przenosi go do góry, wiec możemy wywołać funkcje zanim zostanie ona zadeklarowana
możemy do zmiennej przypisać funkcje, ale wyrażenia funkcji już nie.
Deklaracje dobre dla funkcji górno poziomowych, elementy publiczne np API. Deklaracja powoduje ze funkcje można wywołać w dowolnym miejscu kodu
Wyrażenia lepsze jak funkcje wew, elementy które można przekazywać, lub jak potrzebujemy dynamicznego przypisywania, nie możemy wywoływać jej w dowolnym miejscu funckcji
funkcja nie musi mieć nazwy bo bierze ją ze zmiennej, wyrażenie funkcyjne może to nazwę również posiadać
console.log(powitanie("Jan"));
function powitanie(imie) { // deklaracja funkcji
return "Witaj, " + imie + "!";
}
const pomnoz = function(a, b) { // wyrażenie funkcyjne
return a * b;
};
console.log(pomnoz(2, 3));
// FUNCTION EXPRESSION - nazwana
const podziel = function dzielenie(a, b) {
if (b === 0) {
return "Nie można dzielić przez zero";
}
return a / b;
};
console.log("Dzielenie:", podziel(10, 2)); // 5
console.log(powitanie("Anna")); // Witaj, Anna!
function oblicz(a, b, c) {
console.log("a:", a); // 10
console.log("b:", b); // 20
console.log("c:", c); // undefined
return a + b;
}
oblicz(10, 20);
// Więcej argumentów niż parametrów
function dodaj(a, b) {
console.log("a + b =", a + b);
// Trzeci argument jest przekazany ale nie używany
}
dodaj(5, 15, 25); // a + b = 20
// 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
// funkcja(users[0]);
const users = [
{name: "Jan", age: 25},
{name: "Anna", age: 30},
{name: "Piotr", age: 28}
]Parametry i argumenty
Parametry w deklaracji funkcji, zmienne które są zmiennymi blokowymi, maja scope blokowy
Argumenty to faktyczne wartości które wpisujemy w momencie kiedy wywołujemy funkcje
W JS mozemy miec argumenty tyle ile chcemy ile funkcja ma, może dać więcej czy mniej. Ale jeśli przekazem ich za mało to co się stanie z tymi argumentami wewnątrz fukcji które nie dostały swojej wartości? bedzie np Witaj undefined! otrzymują wartość undefined - to domyślna wartość na JS jeśli żadna wartość nie jest napisana. Null to intencjonalne wpisane null!
JS obsługuje argumenty jako pseudo tablice, to specjalny obiekt. Wygląda jak tablica, ma długość jak tablica ale nie ma metod tablicowych, nie możemy używać na nim funkcji. Jeśli chcemy dostać do listy argumentów które są przekazane do funkcji jesteśmy w stanie to zrobić.
Więcej argumentów -> trzeci jest przekazany ale nie używany.
Obiekt arguments- function sumujWszystkie() tam jest suma2=sumujWszystkie po dopisaniu 6 na końcu doda to do sumy. Możemy się po nim iterować.
Chociaż argumenty tablicą nie są, możemy łatwo skonwertować obiekt arguments na pełnoprawną tablice
// 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
2:27
Return
powinny coś zwracać, nawet chociaz true/false
Funkcja może mieć więcej niż 1 pkt wyjścia, funkcja może mieć więcej returnów - Guard Clauses - instrukcje warunkowe, sprawdzajmy warunki brzegowe jako pierwsze aby nie przechodzić przez cale drzewo decyzyjne, jeśli pobieramy od użytkownika liczbę najpierw sprawdzamy czy to liczba. to samo jest z funkcją jeśli funkcja cos robimy i ma instrukcje warunkowe no to trzeba sprawdzić te podstawowe zachowania.
Funkcja kończy się natychmiast jak napotkamy return, wszystko co jest po return to się nie wykona, wiec to powinna być ostatnia linijka - ten return.
Return może zwracać dowolny typ wartości, wartości prymitywne, stringi, liczby, boolean, obiekty tablice może zwraca inne funkcje. Zwracanie obiektów to powszechna praktyka gdy funkcja ma zwrócić wiele wartości.
Early Returns, Guard Clauses
function sprawdzLiczbę() {
const liczba = arguments[0];
if (typeof liczba !== "number") {
return "To nie jest liczba";
}
if(liczba > 0){
return "Liczba dodatnia";
} else if(liczba < 0){
return {info: "Liczba ujemna"};
} else {
return ['Liczba równa zero'];
}
}
console.log(sprawdzLiczbę(-3)); // "Liczba dodatnia"
function pobierzDane(id) {
if (id < 0) {
return null; // Zwraca null
}
if (id === 0) {
return false; // Zwraca boolean
}
if (id < 10) {
return "Wartość: " + id; // Zwraca string
}
return { id: id, nazwa: "Obiekt" }; // Zwraca obiekt
}Shadowing; Function scope
Zmienne globalne definiujemy na początku zakresu po to aby były dostępne dla każdego innego elementu poniżej w tym dla min. funkcji. Dla zmiennej globalne mimo ze jest cont let możemy do niej sięgnąć w funkcji i korzystać z jej wartości. Jeśli zdefiniujemy zmienna w ciele funkcji to ta zmienna jest dostępna tylko w tej danej funkcji, jest zmienna blokowa.
Scope chaining - dostęp do zewnętrznych zmiennych, JS najpierw szuka zmiennej w lokalnym zakresie czyli w bloku, a jak nie znajdzie to szuka wyżej.
Jeśli w funkcji jest pętla a w pętli zmienna to ta zmienna jest tylko w tej pętli ale poza pętlą już nie.
Nie jechać cały program na zmiennych globalnych - występuje shadowing. Zjawisko jeśli zmienna wewnętrzna nazywa się tak samo jak zmienna globalna. Jeśli w wew. zakresie mamy tak samo nazwana zmienna jak zmienna globalna to może dojść do błędów.
dlaczego JS na to pozwala? Bo gdyby chcieć w tym samym bloku zadeklarować dwa razy zmienną z tą samą nazwą to rzuci błąd
nie ide do sklepu po wode skoro mam wode w domu
// Zmienna globalna
const globalnaZmienna = "Jestem globalna";
function pokazZakres() {
// Dostęp do globalnej zmiennej
console.log(globalnaZmienna); // Działa
const lokalnaZmienna = "Jestem lokalna";
console.log(lokalnaZmienna); // Działa
for (let i = 0; i < 3; i++) {
console.log("i w pętli:", i); // Działa
}
// console.log("i poza pętlą:", i); // BŁĄD! i nie istnieje tutaj
}
pokazZakres();
// Zmienna lokalna
function funkcjaZLokalna() {
const lokalnaZmienna = "Jestem lokalna";
console.log(lokalnaZmienna); // Działa wewnątrz
}
funkcjaZLokalna();
// console.log(lokalnaZmienna); // BŁĄD! Not defined
// Scope chain - dostęp do zewnętrznych zmiennych
const zewnetrzna = "Zewnętrzna";
function zewnetrznaFunkcja() {
const srodkowa = "Środkowa";
function wewnetrznaFunkcja() {
const wewnetrzna = "Wewnętrzna";
// Dostęp do wszystkich poziomów
console.log(wewnetrzna); // Działa
console.log(srodkowa); // Działa
console.log(zewnetrzna); // Działa
}
wewnetrznaFunkcja();
// console.log(wewnetrzna); // BŁĄD! Nie dostępna tutaj
}
zewnetrznaFunkcja();
// Shadowing - zasłanianie zmiennych
const liczba = 100;
function demonstracjaShadowing() {
const liczba = 50; // Zasłania globalną
console.log("Wewnątrz funkcji:", liczba); // 50
if (true) {
const liczba = 25; // Zasłania funkcyjną
console.log("Wewnątrz bloku:", liczba); // 25
}
console.log("Po bloku:", liczba); // 50
}
demonstracjaShadowing();
console.log("Globalna:", liczba); // 100
// Block scope dla let i const
function blockScope() {
if (true) {
const blokowaConst = "Block scope";
let blokowaLet = "Block scope";
console.log(blokowaConst); // Działa
}
// console.log(blokowaConst); // BŁĄD! Not defined
// console.log(blokowaLet); // BŁĄD! Not defined
}
blockScope();
Function naming conventions
- Unikać skrótów pisać pełne nazwy
- czasownik zeby bylo wiadomo co robi
- camelCase
- Boolean is can has should
- spójność używania np jeśli używa get w nazwie funkcji do pobierania danych to trzymać się tego aby było get
- prefixy wskazują na typy operacji get set delete update itd
// DOBRE PRAKTYKI
// 1. Czasowniki dla akcji
function calculateSum(a, b) {
return a + b;
}
function displayMessage(message) {
console.log(message);
}
function updateUserProfile(user, data) {
return { ...user, ...data };
}
// 2. Boolean functions - is/has/can/should
function isValidEmail(email) {
return email.includes("@");
}
function hasPermission(user, permission) {
return user.permissions.includes(permission);
}
function canEdit(user, document) {
return user.id === document.ownerId || user.isAdmin;
}
function shouldShowWarning(age) {
return age < 18;
}
const user = {
id: 1,
isAdmin: false,
permissions: ["read", "write"]
};
console.log("Może edytować?", canEdit(user, { ownerId: 1 })); // true
console.log("Ma uprawnienie?", hasPermission(user, "write")); // true
// 3. CamelCase // calculateTax // calcTax
function getUserDataFromApi() {
return { name: "Jan", age: 30 };
}
function calculateMonthlyPayment(price, months) {
return price / months;
}
// 4. Prefiksy wskazujące operację
// Get - pobierz dane
function getUserById(id) {
const users = [
{ id: 1, name: "Jan" },
{ id: 2, name: "Anna" }
];
return users.find(u => u.id === id);
}
// Set - ustaw wartość
function setUserName(user, name) {
user.name = name;
return user;
}
// Create - stwórz nowy
function createUser(name, email) {
return {
id: Date.now(),
name: name,
email: email,
createdAt: new Date()
};
}
// Find - znajdź element
function findUserByEmail(users, email) {
return users.find(u => u.email === email);
}
// Filter - filtruj kolekcję
function filterActiveUsers(users) {
return users.filter(u => u.active);
}
// Handle - obsłuż zdarzenie
function handleButtonClick(event) {
console.log("Kliknięto przycisk");
}
// 5. Opisowe vs zwięzłe
// DOBRE - jasne co robi
function calculateTotalPriceWithTax(price, taxRate) {
return price * (1 + taxRate);
}
// GORSZE - niejasne
function calc(p, t) {
return p * (1 + t);
}
console.log("Z podatkiem:", calculateTotalPriceWithTax(100, 0.23)); // 123
// 6. Spójność w projekcie
// Jeśli używasz "get", używaj wszędzie
function getData() { return []; }
function getUserInfo() { return {}; }
function getSettings() { return {}; }
// NIE mieszaj:
// function fetchData() { } // fetch
// function retrieveUser() { } // retrieve
// function loadSettings() { } // load
// 7. Kontekst w nazwie
// LEPSZE - jasny kontekst
function validateUserEmail(email) {
return email.includes("@") && email.length > 5;
}
function formatUserDisplayName(user) {
return user.firstName + " " + user.lastName;
}
// GORSZE - za ogólne
function validate(data) { }
function format(input) { }
// 8. Funkcje pomocnicze - można krócej
// W wewnętrznym zakresie dozwolone krótsze nazwy
function processData(items) {
// Pomocnicza funkcja - krótka nazwa OK
const map = item => item * 2;
const filter = item => item > 10;
return items.map(map).filter(filter);
}
// 9. Unikaj negatywnych nazw boolean
// LEPSZE
function isValid() { return true; }
// GORSZE (podwójna negacja w użyciu)
function isNotInvalid() { return true; }
// if (!isNotInvalid()) // Trudne do zrozumienia!
// 10. Praktyczny przykład
const products = [
{ id: 1, name: "Laptop", price: 3000, available: true },
{ id: 2, name: "Mysz", price: 50, available: false },
{ id: 3, name: "Klawiatura", price: 200, available: true }
];
function getAvailableProducts(products) {
return products.filter(p => p.available);
}
function calculateTotalValue(products) {
return products.reduce((sum, p) => sum + p.price, 0);
}
function findProductById(products, id) {
return products.find(p => p.id === id);
}
function isProductAffordable(product, budget) {
return product.price <= budget;
}
const available = getAvailableProducts(products);
const total = calculateTotalValue(available);
const laptop = findProductById(products, 1);
const affordable = isProductAffordable(laptop, 5000);
console.log("Dostępne produkty:", available.length);
console.log("Wartość:", total);
console.log("Laptop dostępny?", affordable);
// Wyświetlenie
document.getElementById("naming").innerHTML =
"Email valid: " + isValidEmail("[email protected]") + "<br>" +
"Z podatkiem: " + calculateTotalPriceWithTax(100, 0.23) + "<br>" +
"Dostępne produkty: " + available.length + "<br>" +
"Łączna wartość: " + total + " zł";zapamiętać:
hoisting
function expression - wyrażenie funkcyjne ma od razu przypisana zmienna
function declaration - tworzymy funkcje i gdzieś tam potem sobie przypisujemy wartości
parametry
argumenty
return
nazwy funkcji
arrow functions
nowoczesna składnia definiowania funkcji, co oferuje - krótszy zapis niż tradycyjne funkcje czy wyrażenia funkcji
Parametr => strzałka, ciało funkcji
(x) => { return x*2;}
nie różnią sie zbyt wiele, w jednym przypadku piszemy function a w tym nie.
W JS jeżeli jest jeden parametr, to te nawiasy można pominąć czyli (x)=> { } można napisać po prostu x=>{} ale lepszym nawykiem jest używanie nawiasów by uniknąć błędów
Dalej jest potrzebne słowo return.
Arrow functions nie maja własnej nazwy są zawsze anonimowe, możemy przypisać je do zmiennej ale nie ma nazwy takiej jak w pełnej deklaracji funkcji.
Jeśli zwracamy obiekty i nie używamy { } od razu a zwracamy obiekt to występuje błąd, że nawiasy klamrowe od obiektu zostaną zinterpretowane jako ciało funkcji a nie obiekt, wiec trzeba w funkcji strzałkowej wpisać obiekt w nawiasy okrągłe
zmienne przypisane funkcje arrow
Metody tablicowe
Destrukcje - usuwanie parametrów z obiektów
Można łączyć arrow functions
Można tworzyć metody wewnątrz obiektu
// Tradycyjna function expression
const tradycyjna = function(x) {
return x * 2;
};
console.log("Tradycyjna:", tradycyjna(5)); // 10
// Arrow function - podstawowa forma
const strzalkowa = (x) => {
return x * 2;
};
console.log("Strzałkowa:", strzalkowa(5)); // 10
// Arrow function - implicit return (bez klamr)
const krotsza = (x) => x * 2;
console.log("Krótsza:", krotsza(5)); // 10
// Jeden parametr - bez nawiasów
const bezNawiasow = x => x * 2;
console.log("Bez nawiasów:", bezNawiasow(5)); // 10
// Zero parametrów - wymagane puste nawiasy
const bezParametrow = () => console.log("Hello!");
bezParametrow();
// Wiele parametrów - wymagane nawiasy
const suma = (a, b) => a + b;
const iloczyn = (a, b, c) => a * b * c;
console.log("Suma:", suma(3, 7)); // 10
console.log("Iloczyn:", iloczyn(2, 3, 4)); // 24
// Wiele instrukcji - wymagane klamry i return
const funkcjaZlożona = (x, y) => {
const temp = x + y;
const wynik = temp * 2;
return wynik;
};
console.log("Złożona:", funkcjaZlożona(5, 10)); // 30
// Zwracanie obiektu - wymaga nawiasów!
// ŹLE - interpretowane jako blok kodu
// const zlyObiekt = () => { id: 1, nazwa: "Test" }; // undefined!
// DOBRZE - obiekt w nawiasach
const dobryObiekt = () => ({ id: 1, nazwa: "Test" });
console.log("Obiekt:", dobryObiekt());
// Praktyczne użycie - metody tablicowe
const liczby = [1, 2, 3, 4, 5];
// Map z arrow function
const podwojone = liczby.map(n => n * 2);
console.log("Podwojone:", podwojone);
// Filter z arrow function
const parzyste = liczby.filter(n => n % 2 === 0);
console.log("Parzyste:", parzyste);
// Reduce z arrow function
const suma2 = liczby.reduce((acc, n) => acc + n, 0);
console.log("Suma reduce:", suma2);
// Arrow function z destrukturyzacją parametrów
const osoby = [
{ imie: "Jan", wiek: 30 },
{ imie: "Anna", wiek: 25 }
];
const imiona = osoby.map(({ imie }) => imie);
console.log("Imiona:", imiona);
// Arrow function wieloliniowa z formatowaniem
const formatujOsobe = ({ imie, wiek }) => {
const opis = imie + " ma " + wiek + " lat";
const kategoria = wiek >= 18 ? "dorosły" : "dziecko";
return opis + " (" + kategoria + ")";
};
console.log(formatujOsobe({ imie: "Piotr", wiek: 20 }));
// Łączenie arrow functions
const dodaj10 = x => x + 10;
const pomnozRazy2 = x => x * 2;
const wynik = pomnozRazy2(dodaj10(5)); // (5 + 10) * 2 = 30
console.log("Łączenie:", wynik);
// Arrow function w metodach obiektu
const kalkulator = {
wynik: 0,
dodaj: (x) => x + x, // UWAGA: this nie działa jak oczekiwano!
pomnoz: (x, y) => x * y
};
console.log("Kalkulator dodaj:", kalkulator.dodaj(5));
console.log("Kalkulator pomnóż:", kalkulator.pomnoz(3, 4));
Domyślne parametry
Można przypisać do funkcji np. imie = "Gość", podobne do prompt, ten gość to wartość domyślna imie
Jeśli potrzebujemy więcej niż 1 parametr, możemy użyć tyle parametrów ile potrzebujemy.
Wartość domyślna może być odwołaniem do wcześniejszych wartości.
Wartość domyślna może też by funkcją.
NULL NIE AKTYWUJE DEFLAUT
// Podstawowe default parameters
function powitaj(imie = "Gość") {
return "Witaj, " + imie + "!";
}
//const imie = prompt("podaj imię", "Jan");
console.log(powitaj("Jan")); // "Witaj, Jan!"
console.log(powitaj()); // "Witaj, Gość!"
console.log(powitaj(undefined)); // "Witaj, Gość!"
console.log(powitaj(null)); // "Witaj, null!"
// Wiele parametrów z defaultami
function stworzUzytkownika(imie = "Anonim", wiek = 18, aktywny = true) {
return { imie, wiek, aktywny };
}
console.log(stworzUzytkownika());
console.log(stworzUzytkownika("Anna"));
console.log(stworzUzytkownika("Piotr", 25));
console.log(stworzUzytkownika("Maria", 30, false));
// Default może być wyrażeniem
function dodajTimestamp(wiadomosc, czas = Date.now()) {
return { wiadomosc, czas };
}
console.log(dodajTimestamp("Test 1"));
// Czekamy chwilę...
console.log(dodajTimestamp("Test 2"));
// Każde wywołanie ma inny timestamp!
// Default odwołujący się do wcześniejszych parametrów
function obliczProstokat(szerokosc, wysokosc = szerokosc) {
// Jeśli wysokość nie podana, kwadrat
return {
szerokosc,
wysokosc,
pole: szerokosc * wysokosc
};
}
console.log(obliczProstokat(5)); // kwadrat 5x5
console.log(obliczProstokat(5, 10)); // prostokąt 5x10
// Default z wywołaniem funkcji
function domyslnaKonfiguracja() {
console.log("Tworzę domyślną konfigurację");
return { timeout: 3000, retries: 3 };
}
function polacz(url, config = domyslnaKonfiguracja()) {
console.log("Łączę z:", url);
console.log("Konfiguracja:", config);
}
polacz("https://api.example.com");
polacz("https://api.example.com", { timeout: 5000 });
// Null NIE aktywuje defaultu
function testNull(wartosc = "domyślna") {
return wartosc;
}
console.log(testNull()); // "domyślna"
console.log(testNull(undefined)); // "domyślna"
console.log(testNull(null)); // null (NIE domyślna!)
// Stary sposób (przed ES6) - problemy
function staryDefault(x) {
x = x || 10; // PROBLEM: 0 i false też aktywują default!
return x;
}
console.log(staryDefault(5)); // 5
console.log(staryDefault(0)); // 10 (nieprawidłowo! 0 jest poprawną wartością)
console.log(staryDefault()); // 10
// Nowy sposób (ES6) - działa poprawnie
function nowyDefault(x = 10) {
return x;
}
console.log(nowyDefault(5)); // 5
console.log(nowyDefault(0)); // 0 (poprawnie!)
console.log(nowyDefault()); // 10
Rest parameters (...args)
- od razu mamy tablice, nie trzeba robić z tego tablicy
- zapis jest ładniejszy ale poza tym niczym się nie rożni
- prawdziwa tablica która przekazuje elementy do funkcji, ma właściwości tablicy, działają metody tablicowe - mapowanie, filtrowanie itd
- tworząc funkcje która ma mieć rest parameters czyli pozostałe argumenty to ten argument pozostały musi być ostatni ????
- rest parameters można użyć z arrow functions
- rest vs arguments, argumenty nie są tablicą - nie można wykonywać operacji tablicowych, nie można na nich używać notacji strzałkowej i trzeba przekonwertować argumenty do tablicy, a rest już jest tablica, przyjmuje dowolna ilość argumentów i można je przetwarzać metodami itp.
// Podstawowy rest parameter
function suma(...liczby) {
console.log("Rest param jest tablicą?", Array.isArray(liczby));
let wynik = 0;
for (const liczba of liczby) {
wynik += liczba;
}
return wynik;
}
console.log(suma(1, 2, 3)); // 6
console.log(suma(1, 2, 3, 4, 5)); // 15
console.log(suma(10)); // 10
console.log(suma()); // 0
// Rest z metodami tablicowymi
function maksimum(...liczby) {
if (liczby.length === 0) return undefined;
return Math.max(...liczby);
}
console.log("Maksimum:", maksimum(3, 7, 2, 9, 1)); // 9
// Łączenie nazwanych parametrów z rest
function przedstaw(imie, nazwisko, ...hobby) {
console.log("Imię:", imie);
console.log("Nazwisko:", nazwisko);
console.log("Hobby:", hobby);
return imie + " " + nazwisko + " lubi: " + hobby.join(", ");
}
const opis = przedstaw("Jan", "Kowalski", "czytanie", "bieganie", "gotowanie");
console.log(opis);
// Rest z arrow functions
const pomnozWszystkie = (...liczby) => {
return liczby.reduce((acc, n) => acc * n, 1);
};
console.log("Iloczyn:", pomnozWszystkie(2, 3, 4)); // 24
// Praktyczny przykład - logger
function log(poziom, ...wiadomosci) {
const timestamp = new Date().toLocaleTimeString();
const polaczone = wiadomosci.join(" ");
console.log("[" + timestamp + "] [" + poziom + "] " + polaczone);
}
log("INFO", "Aplikacja", "uruchomiona");
log("ERROR", "Błąd", "połączenia", "z", "bazą");
log("DEBUG", "Wartość", "=", 42);
// Rest vs arguments
// STARE (arguments)
function staraFunkcja() {
// arguments nie jest tablicą
const args = Array.from(arguments);
return args.reduce((sum, n) => sum + n, 0);
}
// NOWE (rest)
function nowaFunkcja(...args) {
// args jest już tablicą
return args.reduce((sum, n) => sum + n, 0);
}
console.log("Stara:", staraFunkcja(1, 2, 3));
console.log("Nowa:", nowaFunkcja(1, 2, 3));
High order function
funkcja wyższego rzędu - funkcje które przyjmują inne funkcje jako argumenty, same adresują jakieś rzeczy można przekazać do innej funkcji
promuje to reużywalność kodu, zamiast pisać pętle z podobną logiką wielokrotnie, możemy taki proces wyodrębnić do funkcji wyższego rzędu.
Z mały operacji, konstruktów tworzyć złożone operacje, konstrukty. To też pozwala na łatwiejsze rozbicie skomplikowanego problemu na mniejsze elementy. Jeśli mamy bardzo skomplikowaną funkcje, elementy złożone obliczeniowo, trudne do implantacji - możliwe że łatwiej nam będzie jak rozbijemy na mniejsze klocki i zrobimy high order function
można tworzyć swoja bibliotekę takich funkcji
// 1. Funkcja przyjmująca funkcję jako argument
function wykonajOperacje(a, b, operacja) {
return operacja(a, b);
}
const dodaj = (x, y) => x + y;
const pomnoz = (x, y) => x * y;
console.log("Dodawanie:", wykonajOperacje(5, 3, dodaj)); // 8
console.log("Mnożenie:", wykonajOperacje(5, 3, pomnoz)); // 15
// 2. Funkcja zwracająca funkcję
function stworzMnoznik(mnoznik) {
return function(liczba) {
return liczba * mnoznik;
};
}
const pomnozPrzez2 = stworzMnoznik(2);
const pomnozPrzez10 = stworzMnoznik(10);
console.log("5 * 2 =", pomnozPrzez2(5)); // 10
console.log("5 * 10 =", pomnozPrzez10(5)); // 50
// 3. Built-in higher-order functions - map
const liczby = [1, 2, 3, 4, 5];
const podwojone = liczby.map(n => n * 2);
console.log("Podwojone:", podwojone);
// 4. Built-in higher-order functions - filter
const parzyste = liczby.filter(n => n % 2 === 0);
console.log("Parzyste:", parzyste);
// 5. Built-in higher-order functions - reduce
const suma = liczby.reduce((acc, n) => acc + n, 0);
console.log("Suma:", suma);
// 6. Łączenie higher-order functions
const wynik = liczby
.filter(n => n > 2)
.map(n => n * n)
.reduce((acc, n) => acc + n, 0);
console.log("Łączenie:", wynik); // 9 + 16 + 25 = 50
// 7. Własna higher-order function - powtórz
function powtorz(n, akcja) {
for (let i = 0; i < n; i++) {
akcja(i);
}
}
console.log("Powtarzanie:");
powtorz(3, i => console.log("Iteracja", i));
// 8. Własna higher-order function - forEach dla obiektów
function forEachWartość(obiekt, callback) {
for (const klucz in obiekt) {
if (obiekt.hasOwnProperty(klucz)) {
callback(obiekt[klucz], klucz);
}
}
}
const osoba = { imie: "Jan", wiek: 30, miasto: "Warszawa" };
console.log("For each wartość:");
forEachWartość(osoba, (wartosc, klucz) => {
console.log(klucz + ":", wartosc);
});
First Class Citizen
Funkcje są takim obywatelem tzn funkcje mogą być traktowane jak wartości, mogą być przypisywane do zmiennych, przekazywane jako argumenty, zwracane z innych funkcji, mogą być przechowywane w strukturach danych.
Można przechowywać funkcję w strukturach obiektów - tworzenie metody obiektów
do zmiennych - dynamiczne zarządzanie wartością, do jednej zmiennej możemy przypisywać różne funkcje w zależności od potrzeby
funkcje w tablicach
funkcje jako argumenty - callbacks
Funkcje mogą mieć swoje właściwości jako obiekty
W JS połowa albo i więcej programowania opiera się na funkcjach, jest wspierana kierowana by używać funkcji do realizacji logiki
// 1. Przypisywanie funkcji do zmiennych
const powitanie = function(imie) {
return "Cześć, " + imie + "!";
};
console.log(powitanie("Jan"));
// Zmiana przypisania
let operacja = (a, b) => a + b;
console.log("Dodawanie:", operacja(5, 3)); // 8
operacja = (a, b) => a * b;
console.log("Mnożenie:", operacja(5, 3)); // 15
// 2. Funkcje w tablicach
const operacje = [
(a, b) => a + b,
(a, b) => a - b,
(a, b) => a * b,
(a, b) => a / b
];
console.log("Operacje tablicy:");
operacje.forEach((op, i) => {
console.log("Operacja", i, ":", op(10, 2));
});
// 3. Funkcje w obiektach
const kalkulator = {
dodaj: (a, b) => a + b,
odejmij: (a, b) => a - b,
pomnoz: (a, b) => a * b,
podziel: (a, b) => a / b
};
console.log("Kalkulator dodaj:", kalkulator.dodaj(7, 3));
console.log("Kalkulator pomnóż:", kalkulator.pomnoz(7, 3));
// 4. Funkcje jako argumenty - callbacks
function przetworzTablice(tablica, callback) {
const wynik = [];
for (const element of tablica) {
wynik.push(callback(element));
}
return wynik;
}
const liczby = [1, 2, 3, 4, 5];
const kwadraty = przetworzTablice(liczby, n => n * n);
console.log("Kwadraty:", kwadraty);
// 5. Zwracanie funkcji
function stworzFormatter(prefix, suffix) {
return function(tekst) {
return prefix + tekst + suffix;
};
}
const htmlBold = stworzFormatter("<b>", "</b>");
const htmlItalic = stworzFormatter("<i>", "</i>");
console.log(htmlBold("Pogrubiony"));
console.log(htmlItalic("Kursywa"));
// 6. Funkcje z właściwościami
function licznik() {
licznik.wywołania++;
return licznik.wywołania;
}
licznik.wywołania = 0;
console.log("Wywołanie 1:", licznik()); // 1
console.log("Wywołanie 2:", licznik()); // 2
console.log("Wywołanie 3:", licznik()); // 3