Pascal Precht

Dinge die man über JavaScript’s Variablen wissen sollte

Nachdem Ich das Buch “JavaScript Patterns” aus dem O’Reilly-Verlag intensiv bis zur Hälfte studiert habe, möchte Ich ein paar Tipps und Tricks mit euch teilen, die man wissen sollte, wenn man JavaScript Applikationen entwickelt. Aufgrund des Titels dieses Blogposts sollte es nicht schwer zu erraten sein, dass Ich auf Variablen in JavaScript eingehe. Im ersten Moment mag man glauben, dass es nichts weiter über Variablen zu sagen gibt – und keine Sorge, da stimme Ich zu – nur sollte man bei JavaScript eine Ausnahme machen.

Ein paar Worte

Dass man bei JavaScript eine Ausnahme machen sollte, sage Ich nicht weil Ich sehr gerne JavaScript programmiere und diese Sprache aus diesem Grund als etwas “Besonderes” darstellen möchte. JavaScript ist, unabhängig davon wie sehr Ich diese Sprache mag, etwas Besonderes. Das liegt nicht allein daran, dass JavaScript sehr Ausdrucksstark und eine durch und durch objektorientierte und Prototyp-basierte Sprache ist. Allein die Tatsache, dass sich JavaScript völlig anders als andere Sprachen verhält, ist ein Grund dafür zu sagen, dass JavaScript etwas Besonderes ist.

Der Scope

Ein Aspekt, der definitiv zu diesem “aussergewöhnlichen” Verhalten beiträgt, ist der sogenannte Scope. In JavaScript gibt es grundsätzlich erstmal zwei Scopes: Der globale Scope und der Funktions-Scope. Ich möchte an dieser Stelle gar nicht großartig auf den Scope eingehen, Ich erwähne lediglich ein paar Dinge, die von essentieller Bedeutung sind, wenn man die gleich folgenden Tipps und Tricks nachvollziehen möchte. Also, um es kurz zu machen: Variablen, die sich im globalen Scope befinden sind – Überraschung – global verfügbar. Variablen, die innerhalb einer Funktion definiert werden, sind nur im Funktions-Scope verfügbar.
Hier ein Beispiel:

global = "global";    // globale Variable

function foo() {
    var local = "local" // "lokale" Variable
    return global; // "global"
}

Wie Ihr sehen könnt, können wir auf global innerhalb der Funktionsdeklaration zugreifen. local allerdings, wäre außerhalb von foo() nicht erreichbar. So viel dazu. Es gibt also den globalen und den Funktions-Scope.

Das Problem mit den globalen Variablen

Ein besonderer Haken bei der Verwendung von globalen Variablen ist der, dass Sie innerhalb des Funktions-Scopes nach außen hin veränderbar sind. Klingt ein bisschen verdreht, deswegen direkt das nächste Codebeispiel hinterher.

result = 35;    // globale Variable wie bis her

function doSomething() {
    result = 3 + 3;
    return result;
}

doSomething();
console.log(result);    // 6

Es ist nicht schwer zu erkennen. doSomething() verändert den Wert von result. Und jetzt stellt euch mal vor, ihr verwendet in einer Applikation fremde Skripte und wisst nicht, welche Bezeichner für globale Variablen innerhalb dieser Skripte verwendet werden. In so einem Fall lauft ihr Gefahr, globale Variablen der fremden Skripte zu überschreiben.

Wie es richtig geht

Aus diesem Grund soll man den globalen Scope möglichst von Variablen freihalten! Der erste Schritt, dieses  Problem zu umgehen, ist folgender: Deklariert Variablen innerhalb eines Funktions-Scopes immer mit var! Werden Variablen nämlich innerhalb einer Funktion deklariert oder initialisiert, sind Sie nur um Funktions-Scope verfügbar und man vermeidet globale Variablen zu überschreiben. Der eben gezeigt Code, wäre also besser so zu implementieren:

result = 35;

function doSomething() {
    var result = 3 + 3;
    return result;
}

doSomething();
console.log(result);    // 35

Hoisting

Ein weiteres Verhalten, was viele Entwickler nicht kennen, ist das sogenannte Hoisting. Bei der Verwendung von Variablen innerhalb einer Funktion verhält sich JavaScript nämlich anders als andere Sprachen. Wie oben gezeigt, sollte man Variablen innerhalb einer Funktion immer mit var deklarieren, um sie somit in den Funktions-Scope einzuschleusen. Jetzt werft aber mal einen Blick auf den folgenden Code:

global = "global";    // globale Variable
local = "local";

function foo() {
    console.log(global);    // "global"
    console.log(local);    // "undefined"

    var local = "local";    // Initialisierung von 'local'
}

Okay, schräg. Was passiert hier? Man würde vermuten, dass der Aufruf von local “local” ausgibt, da diese Variable doch im globalen Scope definiert ist. Falsch. Wie man sieht, wird innerhalb der Funktion local als lokale Variable initialisiert. Soweit so gut. local befindet sich also im Funktions-Scope (wie gewollt), aber gibt undefined zurück, obwohl bereits ein Wert zugewiesen wurde?!
Das hat folgenden Grund: Hoisting. Wenn der JavaScript-Interpreter auf eine Funktion stößt, überprüft er, welche Variablen verwendet werden und ob diese im entsprechenden Scope vorhanden sind. Ist das nicht der Fall, sucht er im globalen Scope. Im obigen Fall ist local aber im lokalen Scope definiert. Richtig. Nur eben zu einem späteren Zeitpunkt, als wir local aufrufen. JavaScript “zieht” nämlich im Hintergrund sämtliche Variablen, innerhalb einer Funktion, an den Funktionskopf. Die Zuweisung findet an der Stelle aber noch nicht statt, womit local in unserem Fall undefined zurück gibt.

Wie es richtig geht, die Zweite

Um diese Problem zu vermeiden, sollte man Variablen immer am Funktionskopf initialisieren. Somit werden logische Fehler vermieden und es kommt nicht zu diesen hässlichen Seiteneffekten. So würde der Code also in “besser” aussehen:

global = "global";    // globale Variable
local = "local";

function foo() {
    var local = "local";    // Initialisierung von 'local'

    console.log(global);    // "global"
    console.log(local);    // "local"
}

Das Single-Var-Pattern

Okay, nachdem wir nun wissen, wie man mit Variablen umzugehen hat, möchte Ich noch kurz das Single-Var-Pattern vorstellen, welches für ein wenig schlankeren Code sorgt. Also angenommen, wir haben eine Funktion, die Unmengen an Variablen verwendet. Wir wissen, dass wir diese am Anfang unserer Funktionen initialisieren sollten. Nur könnte das zum Beispiel so aussehen:

function myFunc() {
    var a;
    var b = 1;
    var c = 4;
    var name = "Pascal";
    var d = b + c;
    var obj = {};
    var values = [1, 2, 3, 4];

    // Unsere abgefahrene Funktion
}

Das könnte auf Dauer sehr unübersichtlich werden. Um dem ein wenig vorzubeugen, können wir das Single-Var-Pattern verwenden. Das Single-Var-Pattern ist sehr einfach zu implementieren; wir haben nichts weiter zu tun, als die Initialisierungen durch Kommata zu trennen und nur eine var-Anweisung zu verwenden. Demnach würde der Code anschließend so aussehen:

function myFunc() {
    var a,
        b = 1,
        c = 4,
        name = "Pascal",
        d = b + c,
        obj = {},
        values = [1, 2, 3, 4];

    // Unsere abgefahrene Funktion
}

Das wars auch schon! Vergesst nicht das Semikolon hinter der letzten Anweisung.