Pytania rekrutacyjne dla Front-end developerów

  • Główna różnica polega na wiązaniu this:

    • Funkcje strzałkowe nie mają własnego this – dziedziczą je z otaczającego zakresu.
    • Zwykłe funkcje mają własne this, które zależy od sposobu ich wywołania.
    const person = {
      name: "Alice",
      sayHello: function() {
        console.log("Hello, my name is " + this.name);
      },
      sayHelloArrow: () => {
        console.log("Hello, my name is " + this.name);
      }
    };
    
    person.sayHello();      // "Hello, my name is Alice"
    person.sayHelloArrow(); // "Hello, my name is undefined" (bo `this` odnosi się do otoczenia globalnego)
    
    

    Ponadto funkcja strzałkowa:

    • Nie posiada: super, argumens i new.target. Te wartości definiowane są przez najbliższą funkcję niestrzałkową.
    • Nie może zostać wywołana za pomocą new, bo nie posiada metody construct, więc nie może być konstruktorem.
    • Nie posiada właściwości prototype
  • Promise to obiekt w Javascript, który reprezentuje zakończenie (sukces lub porażkę) asynchronicznej operacji i jej wartości. Ponieważ wykonanie niektórych części kodu może zająć trochę więcej czasu, a odpowiedź oczekiwana jest natychmiast (by silnik javascript mógł przejść do wykonania kolejnych części kodu z kolejki), kiedy nie ma jeszcze wyniku, z którym można dalej pracować, zwracana jest obietnica otrzymania tego wyniku. Można wtedy określić, co zrobić z otrzymanym wynikiem w przypadku sukcesu (gdy kod zostanie prawidłowo przetworzony) lub porażki (kiedy kodu nie uda się przetworzyć).

    const promise = new Promise((resolve, reject) => {
      setTimeout(() => resolve("Data loaded"), 2000);
    });
    
    promise.then(console.log).catch(console.error);
    
  • Zwykła tablica indeksowana jest za pomocą liczb (zaczynając od 0), pod każdym kolejnym indeksem zapisane są jakieś dane:

    arr[5, 4, 3, 2, 1]; arr(0) = 5;

    Tablica asocjacyjna zamiast liczb wykorzystuje ciągi znaków. Działa na zasadzie klucz-wartość. W JavaScript tablice nie są asocjacyjne – klucze muszą być liczbami. Do przechowywania par klucz-wartość używa się obiektów:

    const obj = { name: "John", age: 30 };

  • Podwójny znak równości porównuje dane bez względu na ich typ, a potrójny znak równości porównuje jeszcze dodatkowo zgodność typów.

    console.log(1 == '1');    // true
    console.log(1 === '1');   // false 
    console.log(0 == false);  // true 
    console.log(0 === false); // false
  • var – posiada zakres funkcyjny i podlega hoistingowi. Onacza to, że zadeklarowanie zmiennej var w którymkolwiek miejscu funkcji umożliwia odwołanie się do niej, ponieważ jej deklaracja jest wynoszona na początek zakresu funkcji. Ogranicza ją więc zakres funkcji.

    let, const – mają zakres blokowy. Oznacza to, że jeśli zmienna zadeklarowana przy użyciu let lub const użyta zostanie w bloku if {}, to poza tym blokiem będzie niedostępna (w przeciwieństwie do var). Ograniczone są zakresem najbliższego bloku, zdefiniowanego przez nawiasy {}. Ponadto ich deklaracja nie podlega hoistingowi, co oznacza, że nie można się do nich odwołać przed ich zadeklarowaniem (powstaje wtedy TZD – temporal dead zone).

    Zmienna stworzona przy użyciu let nie musi posiadać przypisanej wartości, bo możną ją później zmienić (np. let foo;).

    Stała stworzona przy użyciu const musi od razu posiadać przypisaną wartość (cost foo = "something"), bo nie można jej zmienić (choć można zmodyfikować jej wartość, jeśli jest obiaktem, tablicą – ponieważ const zapobiega zmianie bindowania, czyli zmianie typu wartości, a nie samej wartości).

    const boo = ["gruszka", "jablko"]; 
    boo.push("malina"); 
    console.log(boo) // "gruszka", "jablko", "malina"

  • Funkcja callback to funkcja przekazana jako parametr innej funkcji. Dzięki takiemu podejściu, możliwe jest wskazanie działania, które ma nastąpić, w zależności od wyników działania poprzedniej funkcji. Innymi słowy, jest to funkcja, która ma zostać wykonana po zakończeniu wykonywania innej funkcji:

    function greet(url, callback) {
      console.log("Sending request to:", url);
    
      setTimeout(() => {
        const response = "Hello from API!";
        callback(response); // Call the callback function with the response
      }, 2000);
    }
    
    // Calling greet with a URL and an anonymous callback function
    greet("https://api.example.com", function(response) {
      console.log("Response received:", response);
    });

    W powyższym przykładzie funkcja greet(), wykona jakąć akcję, np. wyśle zapytanie pod wskazany adres URL, a odpowiedź zostanie przekierowana do funkcji anonimowej (callback). W ten sposób, dopiero gdy nadejdzie odpowiedź z zewnętrznego API, dane zostaną wyświetlone w konsoli.

    Bardzo często, aby sprostać wymaganiom biznesowym, będziemy potrzebowali wielokrotnego zagnieżdżania wywołań zwrotnych. Powstały w ten sposób kod jest trudny w utrzymaniu i mało czytelny. Często zdarza się też, że kolejne funkcje zwrotne porozrzucane są w różnych miejscach kodu czy nawet w różnych plikach, co dodatkowo utrudnia pracę. Tego typu sytuację nazywamy piekłem wywołań zwrotnych (ang. callback hell).

    Jak uniknąć callback hell:

    • używajac obietnic
    • używajac async/await
  • Funkcje anonimowe to funkcje bez nazwy, które często używa się w:

    Callbackach – np. w forEach, setTimeout, addEventListener.

    setTimeout(() => {
      // some code
    }, 2000);

    Wyrażeniach funkcyjnych – np. przypisane do zmiennej:

    const hello = function (name) {
      return `Hi, ${name}!`;
    };
    console.log(hello("John"));

    Funkcjach strzałkowych (arrow functions) – krótsza składnia dla funkcji anonimowych:

    const pdouble = x => x * 2;
    console.log(double(5)); // 10

    Natychmiastowo wywoływanych funkcjach (IIFE) – wykonują się od razu po zdefiniowaniu:

    (function () {
      console.log("it will be done immediately!");
    })();
  • Domknięcie to funkcja, która zapamiętuje zakres (wartości zmiennych), w którym została utworzona, nawet jeśli jest wywoływana później, poza tym zakresem.

    W JavaScript każda funkcja tworzy swój własny zakres (scope). Jeśli funkcja jest tworzona wewnątrz innej funkcji, to ma dostęp do zmiennych tej funkcji nadrzędnej, nawet po jej zakończeniu.

    function outerFunction() {
      let count = 0; // Zmienna w zakresie funkcji nadrzędnej
    
      return function innerFunction() {
        count++; // Nadal ma dostęp do count
        console.log(count);
      };
    }
    
    const increment = outerFunction(); // Zwrócona funkcja zapamiętuje `count`
    increment(); // 1
    increment(); // 2
    increment(); // 3

    innerFunction ma dostęp do count, mimo że outerFunction już się zakończyła. To właśnie jest domknięcie – funkcja przechowuje dostęp do swojego zakresu, nawet gdy funkcja nadrzędna przestaje działać.

  • To tablica, która działa na zasadzie klucz-wartość, dzięki czemu odczytywanie danych jest bardzo szybkie i nie zależy od wielkości tablicy. W tablicy mieszającej stosuje się funkcję mieszającą, która dla danego klucza wyznacza indeks w tablicy. W najprostszym przypadku do każdego indeksu przypisany jest jeden klucz, co sprawia, że czas odczytywania danych jest bardzo szybki i wynosi O(1).

    Sprawa komplikuje się, kiedy funkcja mieszająca przypisze ten sam indeks w tablicy dwóm kluczom. Wtedy powstaje tzw. kolizja – sytuacja, w której pod jednym indeksem znajduje się kilka kluczy. Szukając odpowiedniego klucza i przypisanej do niego wartości tablica musi przeszukać kilka elementów przypisanych do tego samego indeksu (tak jakby przeszukiwała drugą tablicę – choć niekoniecznie dane zapisane pod jednym indeksem muszą być w formie tablicy). Czas wyszukiwania danych wydłuża się w takiej sytuacji do O(n) – bo pod danym indeksem trzeba przeszukać n elementów, które są tam przypisane.

    W JavaScript tablicą mieszającą jest np. obiekt lub Map

    const hashtable = {
      "key1": "value1",
      "key2": "value2"
    };
    console.log(hashtable["key1"]); // "value1"
    
    const map = new Map();
    map.set(1, "one");
    console.log(map.get(1)); // "one"
    
    • Delegacja zdarzeń – przypisywanie eventu do rodzica zamiast każdego elementu osobno.
    • Propagacja zdarzeń – sposób, w jaki event przechodzi przez DOM (bubbling lub capturing).