JavaScript Puzzle #1

Co będzie wynikiem poniższego kodu (console.log można zastąpić document.writeln lub czymkolwiek innym – nie jest to istotą zagadki)?

bigQuestion = 'What is the sense of life, universe and everything?';
(function deepThought() {
    console.log(bigQuestion);
    var bigQuestion = '42';
    console.log(bigQuestion);
}());

Zanim uruchomimy kod, spróbujmy przewidzieć wynik.

Wyrażenie postaci (function(){ .. }()); w liniach #2-6 to tzw. funkcja natychmiastowa (IIFE, Immediately-Invoked Function Expression), podpowiem, że nia ma ona tutaj wpływu na rozwiązanie zagadki (zwróć uwagę, że w linii #1 definiujemy bigQuestion jako zmienną globalną).

Spójrzmy co siedzi w środku ‘deepThought’: w linii #3 wypisujemy zawartość zmiennej bigQuestion. Nie została ona wcześniej zadeklarowana, więc zapewne po przejściu po domknięciach (closures) użyta zostanie zmienna globalna o tej samej nazwie i otrzymamy: What is the sence of life, universe and everything?

W linii #4 przysłaniamy zmienną i przypisujemy do niej wartość 42, która to zostanie wypisana w następnej linii.

Jesteśmy już pewni, że wynikiem zagadki jest:

What is the sence of life, universe and everything?
42

Odpalamy kod i co widzimy?

undefined
42

Undefined <- WUT?!

Dlaczego tak?
Tak naprawdę zmienna w linii #3 jest traktowana jako zmienna lokalna, a nie globalna (czy innego zewnętrznego scope’u). W JavaScript możemy deklarować zmienne w dowolnym miejscu w kodzie, ale tak naprawdę zostaną one przeniesione na początek bloku (hoisting).
Nasz powyższy kod jest tożsamy z:

bigQuestion = 'What is the sense of life, universe and everything?';
(function deepThought() {
    var bigQuestion;
    console.log(bigQuestion);
    bigQuestion = '42';
    console.log(bigQuestion);
}());

Jak unikać pułapek hoistingu?
Jednym ze sposobów zapobiegania błędom logicznym spowodowanym przez przenoszenie deklaracji, jest stosowanie wzorca pojedynczego var (wzorca bardziej w rozumieniu guideline niż pattern).
Zaleca on używanie tylko jednego wyrażenia var na początku bloku, deklarującego wszystkie zmienne:

function doSthg() {
    var index = 0,
        context = {},
        separator = "";
    // ...
}

Zauważ, że przy okazji deklaracji zmiennych od razu zdefiniowałem ich domyślne wartości – nie jest to wymagane, ale pomoże to innym lepiej zrozumieć nasz kod (widzimy typ, który mówi nam o przeznaczeniu zmiennej).
Dodatkowo wszystkie zmienne scope’u trzymamy w jednym miejscu, dzięki czemu łatwiej jest nam uniknąć nieświadomego używania zmiennych globalnych.

źródło: “JavaScript Patterns – Build Better Applications with Coding and Design Patterns.”

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s