Computer Keyboard - Głównie JavaScript

Głównie JavaScript

by Jakub T. Jankiewicz

5 pytań na rozmowę rekrutacyjną z języka JavaScript

Kilka dni temu zostałem poproszony o przygotowanie 3 pytań, dla kandydata na programistę Full Stack, do zbliżającej się rozmowy kwalifikacyjnej, z języka JavaScript. Przygotowałem 4 i potem dodałem jeszcze jedno. Oto one.

Co to są domknięcia (ang. closures)?

W skrócie, domknięcia są to funkcje, które mają dostęp do środowiska, w którym zostały zdefiniowane. Pytanie z domknięć to chyba najpopularniejsze pytanie na rozmowach rekrutacyjnych.

Popatrz na poniższy kod:

function a() {
  var b = 10;
  return function() {
     return b;
  };
}
c = a();
c()
// -> 10

Wywołanie funkcji c() zwróci liczbę 10, mimo że zakres dla zmiennej b się skończył, kiedy skończyła się funkcja a. Zmienna b jest nadal jednak dostępna wewnątrz funkcji wewnętrznej, dzięki czemuś o nazwie domknięcia.

Mówiąc dokładnie domknięcie to funkcja, która ma dostęp do środowiska, w którym została zdefiniowana. Co najlepiej jest widoczne, gdy zwracamy funkcje (czyli w funkcjach wyższego rzędu).

Co to jest hoisting?

Deklaracje funkcji i zmiennych są przenoszone na początek funkcji, w której zostały zdefiniowane, ale już przypisanie nie np:

var x = 5;
function foo() {
   
   console.log(x);
   var x = 10;
}
foo();

Jest konwertowane przez engine JavaScript na

var x = 5;
function foo() {
   var x;
   
   console.log(x);
   x = 10;
}
foo();

Dlatego powyższe wywołanie console.log nie zwróci wyjątku ReferenceError, ani nie wyświetli wartości 5, tylko wypisze wartość undefined, ponieważ zmienna bez przypisania ma właśnie wartość undefined.

Dokładny mechanizm wygląda tak, że interpreter „przechodzi” kod dwa razy, za pierwszym „wyciągając” wszystkie deklaracje. Dlatego przy drugim przejściu są na początku bloku, ponieważ są już stworzone wewnętrzne referencje. Tak naprawdę hoisting, nie jest to coś, co znajduje się w specyfikacji ECMAScript (nigdzie nie występuje słowo hoisting), oraz zaimplementowane w silnikach JavaScript, ale wyjaśnienie działania, któremu nadano taką właśnie nazwę.

Czy taki kod wyświetli liczbę 0, jeśli nie dlaczego?

var funs = [];
for (var i = 0; i < 10; ++i) {
   funs.push(function() {
      console.log(i);
   });
}

funs[0]();

Nie zadziała, wyświetli 10, tak jak wywołanie każdej z funkcji, ponieważ w języku JavaScript zakres var nie jest blokowy (czyli tylko dla bloku for w przykładzie) tylko funkcyjny. Każda pętla w forze ma tą samą zmienną. Dlatego na końcu jak się skończy for, każda funkcja będzie miała referencje to tej samej zmiennej, która ma wartość 10.

Aby to naprawić wystarczy użyć let z ES6:

var funs = [];
for (let i = 0; i < 10; ++i) {
   funs.push(function() {
      console.log(i);
   });
}

funs[0]();

albo stworzyć funkcje ze zmienną i, czyli IIFE, który utworzy nowy scope (ang. zasięg) dla tej zmiennej, to rozwiązanie będzie bardziej przenośne między przeglądarkami:

for (var i = 0; i < 10; ++i) {
   (function(i) {
       funs.push(function() {
           console.log(i);
       });
   })(i);
}

Czy ten kod zadziała?

function Foo(number) {
    this.number = number;
    this.add = function(array) {
        return array.map(function(number) {
            return this.number + number;
        });
    }
}

var a = new Foo(10);
console.log(a.add([1,2,3,4]));

Nie ponieważ this w funkcji map będzie to obiekt window albo zwróci wyjątek TypeError jeśli będzie użyty "strict mode", aby to naprawić można użyć tzw. funkcji strzałkowej (ang. arrow function) z ES6 w map:

function Foo(number) {
    this.number = number;
    this.add = function(array) {
        return array.map((number) => {
            return this.number + number;
        });
    }
}

lub zapisać this do zmiennej (bardziej przenośne rozwiązanie) np.:

function Foo(number) {
    this.number = number;
    this.add = function(array) {
        var self = this;
        return array.map(function(number) => {
            return self.number + number;
        });
    }
}

Co wyświetli poniższy kod?

var array = [2,3,4,5];
for (var i in array) {
    console.log(i);
}

Wyświetli:

0
1
2
3

for..in operuje na kluczach. W przypadku tablic są to indeksy, aby iterować po wartościach tablicy, można użyć normalnego fora:

var array = [2,3,4,5];
for (var i=0; i<array.length; ++i) {
    console.log(array[i]);
}

Albo użyć pętli z ES6 for..of:

var array = [2,3,4,5];
for (const i of array) {
    console.log(i);
}

można także użyć funkcji forEach z ES5.

źródło strony (aby zobaczyć kod na GitHubie, musisz kliknąć przycisk raw)
Komentarze

Hasło, które podasz umożliwi ponowne zalogowanie się i np. usunięcie komentarza, jest dobrowolne. Email jest szyfrowany i używany do wysyłania powiadomień o odpowiedziach do komentarzy oraz do pobierania awatara dzięki usłudze Gravatar.com.