Frontend - kompendium wiedzy

Czym jest CSS?

CSS

Cascading Style Sheets - jest to język, używany w celu określenia części wizualnej strony, wyglądu i ułożenia poszczególnych elementów. Potocznie mówimy, że coś stylujemy.

Na frontendzie stylowanie najczęściej związane jest z językiem HTML oraz SVG. Możemy myśleć o tym, jak o urządzaniu mieszkania, w którym malujemy ściany czy układamy posiadane już meble w odpowiedni sposób.

Najnowszą oficjalną i stale rozwijaną wersją, jest CSS3.

Czym jest HTML?

HTML

HyperText Markup Language - jest to język rozumiany przez przeglądarki internetowe, umożliwiający wyświetlanie treści w odpowiednio zorganizowany sposób.

Tworzymy szablon strony z odpowiednich elementów (np div, p, header, section) tak, aby całość miała logiczny sens. Możemy to traktować jak strona tytułowa w gazecie, pozbawiona jakichkolwiek wizualnych dodatków.

Najnowsza oficjalna wersja to HTML 5, która wprowadziła sporo nowości i jest uznawana za bieżący standard.

Czym jest koercja?

JavaScript

Jest to niejawna konwersja jednego prymitywnego typu na drugi, na przykład ze string na number.

javascript
const a = '1'
const b = 5
console.log(a + b)

Silnik Javascriptu wykonuje koercje na liczbie 5, przekształcając ją w tekst i następnie łączy dwa teksty w jeden dając w rezultacie wynik 15.

Czym są dumb i smart komponenty?

JavaScript

Smart komponenty wykonują logiczne operacje, strzelają po dane, modyfikują je i przesyłają przygotowane wartości do dumb komponentów. Nie odpowiadają za wyświetlanie informacji i nie zawierają styli CSS. Często smart komponenty nazywamy kontenerami. To podejście jest najczęściej stosowane w JSowych frameworkach jak React czy Angular.


Dumb komponenty natomiast są to komponenty, których rolą jest wyłącznie wyświetlenie otrzymanych danych. Zawierają HTML i CSS. Nie używają zewnętrznych zależności (bibliotek), z wyjątkiem tych odpowiedzialnych za prezentacje informacji.


Smart Component

javascript
function SmartComponent() {
  // strzał do API
  const data = fetch('https://api.jakas.strona').then(response => response.json())

  // przekazanie danych do DumbComponent
  <DumbComponent data={data} />
}

Dumb Component

javascript
export function DumbComponent(props) {
    return(
        <div className="dumb-component-value">
          {props.value}
        </div>
    )
}

Funkcje pure i impure

JavaScript

Czysta funkcja to taka, która przyjmując te same argumenty wypluwa ZAWSZE ten sam wynik i nie produkuje żadnych efektów ubocznych (side effects).

Idealny przykład czystej funkcji:

javascript
function pureFunctionAdd(firstNumber, secondNumber) {
  return firstNumber + secondNumber
}

Funkcja pureFunctionAdd zwraca nic innego jak sumę dwóch liczb podanych jako argumenty. Na zewnątrz funkcji nic nie zmieniamy, nie usuwamy, nie kombinujemy, po prostu dodajemy dwie otrzymane liczby do siebie. Dużą zaletą takich funkcji jest czytelność i łatwość testowania.


Przykładem brudnej funkcji, z która pewnie nie raz się spotkacie, jest Math.random(). Za każdym razem gdy wywołamy funkcje zwraca ona losowo liczbę w zakresie od 0 do 1. Nie mamy wpływu jaka liczba zostanie zwrócona.

javascript
Math.random() // 0.7660832116056935
Math.random() // 0.9947782934931475
Math.random() // 0.6818396543291918

Aby bardziej uplastycznić sobie brudną funkcje, spójrzcie na przykład poniżej:

javascript
let value = 0

function putAnyNumber(number) {
  value = value + 1
  return number + value
}

Mamy globalną zmienną value z przypisaną wartością 0 oraz funkcje putAnyNumber. Zobaczcie co się stanie, gdy wywołamy tą funkcje kilkukrotnie, z takim samym argumentem 1.

javascript
putAnyNumber(1) //pierwsze wywołanie zwraca 2
putAnyNumber(1) //drugie wywołanie zwraca 3
putAnyNumber(1) //trzecie wywołanie zwraca 4

putAnyNumber jest przykładem brudnej funkcji, ponieważ ma efekt uboczny. Efektem ubocznym jest inkrementowanie (zwiększanie) zmiennej value. Mimo, że za każdym razem podajemy, jako argument, tą samą liczbę, to zwracany wynik NIE JEST taki sam - ze względu na zmieniającą się wartość value.

let, const, var

JavaScript

let, const i var są rodzajami zmiennych w JavaScript. Przypisujemy do nich różne wartości, w celu ich późniejszego wykorzystywania. Zmiennej let używamy, gdy wiemy, że zapisane w niej początkowo dane ulegną zmianie.

javascript
let example = 'Hello'
console.log(example) // Hello
example = 1
console.log(example) // 1

Zmiennej const używamy, gdy wartość przypisana nie ulegnie zmianie. Dlatego ten typ jest czasem nazywany 'stałą'. Wyjątek stanowią:

  • tablice — zawartość da się modyfikować wbudowanymi metodami tablicy (np. push, pop, shift).
  • obiekty — elementy wewnątrz obiektu są otwarte na modyfikacje.
javascript
const example = 'Hello'
console.log(example) // Hello
example = 1 // Uncaught TypeError: Assignment to constant variable.

let i const działają w zakresie blokowym. Oznacza to, że ich dostępność ograniczona jest przez klamry { const zmienna = 1 }, poza którymi są niewidoczna.

var działa podobnie jak let z tą różnicą, że jej zasięg jest ograniczony przez funkcje, a nie przez blok. Co więcej, var zadeklarowana poza funkcją, zyskuje zakres globalny. Innymi słowy, mamy do niej dostęp z każdego miejsca w aplikacji. Z powodów złożoności zakresu, jak i bezpieczeństwa, zmienne var nie są rekomendowane i w ich miejsce powinny być używane odpowiednio const lub let

Mechanizm CORS

Bezpieczeństwo

Cross-origin resource sharing - jest to mechanizm, który pozwala na komunikację HTTP naszej strony z zewnętrznymi źródłami.

Domyślnie w przeglądarkach, obowiązuje zasada jednego pochodzenia (tzw. SOP). Jeżeli mamy stronę pod adresem https://xyz.pl, to możemy pobierać dane wyłącznie w zakresie tej domeny. Co więcej, ograniczenia dotyczą nie tylko domeny, ale także portu czy protokołu (http://xyz.pl:3000 — będzie poza zasięgiem).

Jest to związane z bezpieczeństwem. To tak, jak do swojego domu wpuszczamy tylko zaufane osoby, a listę tych osób mamy spisaną w specjalnym notesie.

Notesem jest CORS, domem — nasz serwer www, a gościem jest strona w przeglądarce, która próbuje wejść do środka po różne dane.

Przykładowa konfiguracja CORS po stronie serwera wygląda w ten sposób:

http
Access-Control-Allow-Origin: https://cc.xyz.pl
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type 

Nasza strona przy próbie pobrania danych przekazuje informacje o sobie w nagłówkach (headers). Serwer sprawdza, czy otrzymane dane pokrywają się ze zdefiniowaną polityką CORS i jeżeli wszystko będzie się zgadzać, to zwróci odpowiednie dane. W przeciwnym razie otrzymamy błąd.

Czym jest optional chaining (?.) ?

JavaScript

Operator ?., nazywany optional chainingiem, pozwala nam odczytać zagnieżdżone elementy w obiekcie zabezpieczając się przy tym przed ewentualnym wystąpieniem po drodze wartości zerowych (nullish) takich jak undefined czy null. Zaletą wykorzystania tego operatora jest to, że nie wywołuje on błędu gdy wartość do której próbujemy się odwołać jest wartością zerową (nullish) ale zwraca undefined

Stwórzmy prosty i nieskomplikowany obiekt car posiadający takie wartości jak brand, color oraz engine. Zauważ, że engine jest to zagnieżdżony obiekt, który posiada takie wartości jak cc oraz type.

javascript
const car = {
  brand: 'Audi',
  color: 'White',
  engine: {
    cc: 2000,
    type: 'diesel'
  }
}

Spróbujmy teraz przypisać do zmiennej horsePower nieistniejącą wartość hp z zagnieżdżonego obiektu engine

javscript
const horsePower = car.engine?.hp

console.log(horsePower) // undefined

Zgodnie z tym co napisaliśmy wyżej, próba dostania się do nieistniejącej wartości w obiekcie, dzięki wykorzystaniu operatora ?., zakończyła się przypisaniem do zmiennej horsePower wartości undefined gdyż obiekt car nie posiada wartości hp.

Spróbujmy wywołać w takim razie nieistniejącą funkcję w obiekcie car.

javascript
console.log(car.fireUpEngine?.()) // undefined

Rezultat jest przewidywalny. Odwołanie się do nieistniejącej funkcji w obiekcie car skutkuję zwróceniem wartości undefined. Powyższy zapis na pierwszy rzut oka może wydawać się trudny, jednak nie ma tam nic skomplikowanego. Czytamy go w następujący sposób: jeżeli w obiekcie car istnieje funkcja fireUpEngine -> car.fireUpEngine?. to ją wywołaj ().

Różnica pomiędzy splice() i slice()

JavaScript

Splice wycina kawałek tablicy, modyfikując ją. Następnie zwraca wycięty kawałek, który możemy np przypisać do nowej zmiennej.

javascript
const array = [1, 2, 3, 4]
console.log(array.splice(2)) // [3, 4]
console.log(array) // [1, 2]
  • Pierwszy log zwróci wynik [3,4], ponieważ wywołując slice(2) ucięliśmy tablice zaczynając od drugiego indeksu tabeli (przypomnijmy, że indeks tablicy liczymy od 0).
  • Drugie wywołanie zwróci nam [1,2] - czyli elementy, które pozostały w oryginalnej tablicy.

Slice kopiuje kawałek tablicy, NIE wpływając na jej początkową zawartość.

javascript
const array = [1, 2, 3, 4]
console.log(array.slice(2)) // [3, 4]
console.log(array) // [1, 2, 3, 4]
  • Pierwszy log zwróci skopiowane elementy z tablicy [3, 4],
  • Drugie wywołanie wyświetli niezmienioną tablice wejściową czyli [1, 2, 3, 4].

Podsumowując:

Obie metody wywołujemy na tablicach i obie zwracają wycięte lub skopiowane elementy w formie nowej tablicy.

Różnica polega na tym, że splice wycina elementy z oryginalnej kolekcji, a slice je kopiuje.

Substring, substr i slice

JavaScript

Wszystkie trzy metody, są wywoływane na danych tekstowych (string). Nie modyfikują oryginalnej wartości, zamiast tego zwracają nowy string.

  • Substring Przyjmuje dwa parametry numeryczne (indeksy znaków) i zwraca to, co znajduje się w podanym zakresie. Pierwszy parametr jest wymagany, drugi opcjonalny (domyślnie jest to wartości).
javascript
const text = 'Frontend'
console.log(text.substring(0)) // 'Frontend' - zwracamy całą wartość
console.log(text.substring(-3)) // 'frontend' - pierwszy parametr ujemny działa jak 0
console.log(text.substring(1, 5)) // 'ront' - zakres zaczynamy od drugiego i kończymy na piątym indeksie
console.log(text.substring(-3, -1)) // '' - ujemne indeksy działają jak 0
console.log(text.substring(8, 5)) // 'end' - parametry można podawać w odwrotnej kolejności, najważniejszy jest zakres.

  • Substr Przyjmuje dwa parametry. Pierwszym parametrem jest indeks, od którego rozpoczyna się nowa wartość. Drugi parametr to ilość znaków, które mają być wyciągnięte (domyślnie do końca początkowej wartości).
javascript
const text = 'Frontend'
console.log(text.substr(0)) // 'Frontend' - zwracamy całą wartość
console.log(text.substr(-3)) // 'end' - pierwszy parametr może być ujemny
console.log(text.substr(1, 5)) // 'ronte' - 5 znaków zaczynając od drugiego indeksu.
console.log(text.substr(-3, -1)) // '' - długość nie może być ujemna
console.log(text.substr(8, 5)) // '' - nie można podawać zakresu w odwrotnej kolejności
  • Slice Przyjmuje dwa parametry. Tak samo jak substring - początkowy oraz końcowy indeks.
javascript
const text = 'Frontend'
console.log(text.slice(0)) // 'Frontend' - zwracamy całą wartość
console.log(text.slice(-3)) // 'end' - pierwszy parametr może być ujemny
console.log(text.slice(1, 5)) // 'ront' - zakres zaczynamy od drugiego i kończymy na piątym indeksie
console.log(text.slice(-3, -1)) // 'en' - można bazować na ujemnych indeksach
console.log(text.slice(8, 5)) // '' - nie można podawać zakresu w odwrotnej kolejności

Czym inferencja typów (Type Inference) ?

TypeScript

Inferencja jest to mechanizm TypeScript'u, który na podstawie przypisanej wartości sam, niejawnie, przypisuje odpowiedni typ danej zmiennej.

typescript
const x = 3
console.log(typeof x) // 'number'

const y = 'hello world'
console.log(typeof y) // 'string'

W niektórych sytuacjach pozostawienie TypeScriptowi możliwości określenia danego typu w sposób niejawny jest w porządku jednak nie powinniśmy zawsze polegać na inferencji. W większości przypadków znacznie bezpieczniejsze jest jednak jawne określanie typu który przechowywać będzie nam dana zmienna.