JavaScript-Variablen im Wiki-Projekt


Diese Seite diskutiert die allgemeine Handhabung von Variablen und anderen Datenelementen im JavaScript-Code für Wiki-Projekte. Zu bestimmten MediaWiki-Variablen siehe: JS/Variablen.

Generierung

Bearbeiten

Eine Variable kann auf eine von drei Arten entstehen:

  1. Ein bisher unbenutzter Variablenname wird verwendet. (mehr dazu)
  2. Eine Deklaration mit var.
  3. Die Definition einer benannten function generiert eine Variable, deren Wert ein spezielles Funktionsobjekt ist.
    • In den Browsern wird die so entstandene Variable unter dem Funktionsnamen meist so behandelt, als ob sie mit var deklariert worden wäre.
    • Die Argumente einer Funktion lassen ebenfalls innerhalb dieser Funktion Variablen entstehen.

Eine ohne var entstandene Variable lässt sich möglicherweise mit delete löschen; mit var ist das nicht möglich.

Nicht als Variable betrachtet werden hier die Komponenten eines Objekts.

Der Scope ist der Gültigkeitsbereich, in dem eine Variable bekannt ist und ihr Wert gesetzt und verwendet werden kann.

Global scope

Bearbeiten

Zum „globalen Gültigkeitsbereich“ gehört:

  1. Jede Variable, die nicht innerhalb einer Funktion mit var deklariert wird oder Argument ist.
  2. Jede Variable, die nicht Komponente eines Objekts ist.

„Global“ meint: Alle Skripte, die bisher irgendwann in die HTML-Seite eingebunden wurden, nicht nur eine Seite mit einem einzelnen Benutzerskript.

Function scope

Bearbeiten
  • Gültigkeit ausschließlich innerhalb einer Funktion haben solche Variablen, die innerhalb der Funktion mit var deklariert sind.
  • Gibt es mit gleichem Namen eine globale Variable, wird die mit var deklarierte Funktionsvariable verwendet (Maskierung).
  • War ansonsten zuvor eine globale Variable implizit oder explizit entstanden, wird diese verwendet und ggf. auch verändert.
  • Ist die Variable innerhalb der Funktion nicht mit var deklariert, entsteht dadurch bei Ausführung der Funktion eine globale Variable.

Die Beschränkung der Gültigkeit auf die Funktion nennt man auch “closure”.

  • Die Argumente der Funktion haben innerhalb dieser Funktion lokale Gültigkeit, als ob sie mit var vereinbart wären.

Clause scope

Bearbeiten

In der folgenden try-catch-clause definiert sich automatisch eine Variable, hier e:

   try {
      dubioscall();
   } catch (e) {
      window.alert("Fehler: " + e);
   }

Die Variable e ist lokal in der catch clause. Innerhalb dieses Blocks kann sie ausgewertet werden; anschließend existiert sie nicht mehr. Andere Variablen gleichen Namens würden vorübergehend verdeckt werden, aber man sollte unnötige Namensdopplungen vermeiden.

Block scope

Bearbeiten

Vorweg: Anders als in anderen Programmiersprachen gibt es in JavaScript keinen Block scope.

Ein Beispiel in C:

   int XY(int elems[])
   {
      int k  =  0;
      // ...
      {
         size_t n  =  sizeof(elems) / sizeof(int);
         // ...
         for (size_t i = 0;  i < n;  i++) {
            k  =  10 * k  +  elems[i];
         }   // for i
         // ...
      }
      // ...
      return (k);
   }

Hier ist die Variable n nur in den umschließenden Klammern bekannt, der Schleifenindex i nur innerhalb der Schleife; außerhalb sind n und i undeklariert und würden zu einem Syntaxfehler führen.

In Anlehnung daran schreiben manche Programmierer in JavaScript, wenn angedeutet werden soll, dass die Verwendung von i nur innerhalb der Schleife beabsichtigt ist:

   function XY(elems) {
      var k  =  0;
      var n  =  elems.length;
      // ...
      for (var i = 0;  i < n;  i++) {
         k  =  10 * k  +  elems[i];
      }   // for i
      // ...
      return k;
   }

Hier wirkt sich die Deklaration var i aber über die gesamte Funktion aus.

Damit auch Monate später und andere Bearbeiter auf Anhieb die Codierung erfassen, sollten deshalb alle var am Beginn des scope gesammelt werden; dies entspricht dann auch der tatsächlichen Wirkung.

Zwar gibt es keinen Block scope, jedoch die Möglichkeit, einen Block in runde Klammern zu setzen, damit eine Pseudo-Funktion zu bilden und diese dann „aufzurufen“.[1] Das entspräche einer anonymen Funktion:

   ( function ( w ) {
           "use strict";
           if ( ! w.what ) {
              w.what  =  {};
           }
   } ( window ) );

Der Informationsfluss wird kontrolliert; das Innere das Blocks kann auf die Umgebung zugreifen, aber das Innere ist von außen nur über die aufgezählten Parameter zugreifbar.

Stack scope

Bearbeiten

Immer dann, wenn durch Multithreading mehrere Prozesse entstehen, kann es zu mehrfachem Global scope kommen – eine Art Paralleluniversum. In einem Browser kann das geschehen, wenn

  • window.setTimeout() oder window.setInterval() benutzt wird.
  • Skripte asynchron geladen werden, wie das seit Anfang 2011 durch Google Chrome und seit Mitte 2011 durch Firefox praktiziert wird.

In dem Moment, in dem ein Kindprozess ausgebrütet wird, bekommt es eine vollständige Kopie des Global scope mit auf den Weg. Der Elternprozess behält seinen Global scope. Ändert der Kindprozess den Wert einer Variablen oder fügt eine hinzu, bekommt der Elternprozess davon nichts mit; genauso kann der Elternprozess unabhängig agieren, ohne dass der Kindprozess etwas bemerkt. Später eintreffende Ereignisse werden an den verursachenden Prozess dirigiert. – Oder es wurde beim Wiederaufnehmen das momentane globale Objekt durch das eingefrorene globale Objekt überschrieben. Damit würden zwischenzeitlich eingetroffene Änderungen überschrieben, die durch Ereignisse wie eintreffende Ajax-Abfragen oder asynchrones Laden bewirkt worden waren.

In den Standards auch der ECMA zu JavaScript ist dazu nichts festgelegt.[2] Die Programmierer von Browsern sind frei, wie sie mit der Situation umgehen wollen. Zurzeit (2012) sind auch die Debugger wie Firebug bei der Darstellung der Variablen nicht in der Lage, die unterschiedlichen Global scopes zu unterscheiden; man sieht anscheinend immer den ursprünglichen Elternprozess.

Der einzig erwiesenermaßen sichere Weg ist es, das window-Objekt und seine weiteren Komponenten zu verwenden. Hiervon gibt es genau eines, und bei der Ansprache der jeweiligen Komponente eines Objekts wird auf dessen übergeordnetes Objekt zugegriffen.

Vielleicht gibt es ja irgendwann eine Lösung in allen Browserversionen, die wieder nur einen einzigen Global scope pro Fenster kennt. Mit Umsetzung des "use strict" aus ECMA 3/5 in den Browsern sollte es mittelfristig dazu kommen, dass jedes globale Objekt immer auch auf genau ein und dasselbe window-Objekt abgebildet ist. Auch wenn alle Browser-Familien dies berücksichtigen sollten, wird es noch auf lange Zeit abweichende Browser-Versionen bei den Benutzern geben. Die Programmierung innerhalb der Wiki-Projekte muss deshalb der Möglichkeit Rechnung tragen.

window scope

Bearbeiten

JavaScript in einem Wiki-Projekt ist dafür vorgesehen, dass es in einem Fenster/Tab eines Browsers abläuft. In einem Browser-Fenster ist immer das globale Objekt window definiert. Das muss in JavaScript nicht so sein; man kann ein Skript auch lokal auf dem Rechner ohne Browser starten (etwa mit JScript). Deshalb machen die Standards zu ECMA/JavaScript auch keine Vorgaben, wie das window-Objekt und globale Variablen zusammenhängen. Definiert ist lediglich für JavaScript, wie Browser-Aktivitäten und das HTML-Dokument mit dem window-Objekt verbunden sind.

Übrigens ist nicht nur in einem Browser-Fenster, sondern innerhalb dessen auch in jedem iframe ein eigenständiges globales Objekt erforderlich.

Firefox stellt alle globalen Variablen automatisch auch als Komponenten von window dar; der Internet Explorer von Microsoft macht dies nicht. Ob sie aber echte (und “configurable”) Komponenten von window sind, ist auch in den Firefox-Versionen nicht ganz klar. Deshalb ist es dringend ratsam, beabsichtigte globale Variablen explizit unmittelbar oder mittelbar zu Komponenten von window zu machen.

Im Zusammenhang mit Greasemonkey und Add-Ons kann es dazu kommen, dass es außerhalb des Browser-Fensters, in dem das HTML-Dokument wirksam ist, ein oder mehrere weitere globale Objekte gibt, die erweiterte Zugriffsrechte haben und auf das window-Objekt des DOM/HTML-Dokuments zugreifen. Sie heißen möglicherweise ebenfalls window und lassen sich an der Zugriffsmöglichkeit auf das DOM-window erkennen.

Kapselung für Wiki-Projekte

Bearbeiten

Bei neueren Programmierungen in einem Wiki-Projekt soll der Code immer als function scope wie folgt gekapselt werden:

( function ( mw, $ ) {
   "use strict";
   // ...
   // ...
}( window.mediaWiki, window.jQuery ) );

Wirkung:

  • Alle inneren Deklarationen von Variablen und Funktionen haben keine Wirkung nach außen und stören dort nicht.
  • Parallele Skripte, etwa durch Greasemonkey oder Benutzerskripte, beeinflussen das Innere der Kapsel nicht.
  • Nur wenn durch andere Skripte die Namen mediaWiki oder jQuery überschrieben würden, käme es zum Konflikt.
  • Die Variablen mw und $ bezeichnen innerhalb der Kapsel dieselben Objekte wie die jeweiligen Langformen. Nur diese Namen sollten benutzt werden, um sie bei der Suche nach Zeichenketten leicht auffinden zu können. Außerdem werden dadurch die Programmzeilen kürzer.
  • Andere Variablen mit den zufällig gleichen, wenig spezifischen zwei Buchstaben mw oder auch $ werden durch das Innere der Kapsel nicht berührt.
  • Die Objekte mediaWiki wie auch jQuery sind eine Eigenschaft des Objektes window und damit eindeutig in der aktuellen Browser-Seite, auch wenn sich bei asynchroner Bearbeitung mehrere Instanzen ergeben.

Anmerkung: Von mediawiki.org wird gelegentlich empfohlen, als dritten Parameter undefined zu deklarieren. Dies ist jedoch unzulässig; undefined ist ein reserviertes Schlüsselwort und darf nicht als Funktionsargument verwendet werden.

var zur Deklaration

Bearbeiten

Die Zuweisung mit var ist in JavaScript die einzige durchgehend echte Deklaration; im Unterschied zu anderen Programmiersprachen, die wesentlich mehr deklarative Anweisungen kennen. Alle anderen Syntaxelemente in JavaScript sind (zumindest bei vielen Browser-Versionen) tatsächlich ausführbare Anweisungen. Nur die function-Deklaration (jedoch im Unterschied zur function-Zuweisung) soll zukünftig immer analog gehandhabt werden.

Gültigkeitsbereich

Bearbeiten

Egal, an welcher Stelle im Scope das var-Statement steht – Deklarationen wirken sich immer auf den vollen Gültigkeitsbereich aus (function oder global).

Das gilt auch, wenn das var-Statement in eine if-Konstruktion eingeschlossen wird:

 if (lucky) {
    var x = 1;
 }

Im globalen Gültigkeitsbereich wäre x überall definiert, würde eine vorherige Definition auch maskieren. Nur im Fall lucky würde zusätzlich der Wert 1 zugewiesen.

Um den Überblick über die in einer Einheit (global oder function) definierten var-Statements zu behalten, empfiehlt es sich dringend, sie ganz am Anfang anzuordnen. Wenn es viele sind, sollten sie auch inhaltlich oder alphabetisch sortiert werden. Wenn ihr Name nicht für sich selbst spricht, ist es ratsam, auch gleich noch einen Kommentar mit Erklärung anzuhängen. Es ist auch der geeignete Ort für eine Initialisierung.

Formatierung

Bearbeiten

MW praktiziert die Anordnung in einem einzigen Statement, durch Kommata getrennt (Coding conventions).

Dem kann man folgen oder auch nicht. Lässt man jede Deklaration als gesondertes Statement einzeln in einer Zeile stehen,[3] kann man alle Zeilen filtern, die den Variablennamen enthalten oder mit var beginnen, und so einen Gesamtüberblick bekommen. Auch die gleichzeitige Initialisierung wird damit übersichtlicher.

Mehrfache Zuweisung

Bearbeiten

JavaScript erlaubt mehrere Zuweisungen in einem Statement. Immer wieder kommt es dabei zu Programmierfehlern:

  var x = y = 100;

Sowohl x wie auch y erhalten den Wert 100. Aber nur x ist mit var deklariert und in einer Funktion eine lokale Variable. Dagegen ist y jetzt eine globale Variable, was in der Funktion wohl nicht beabsichtigt war und dem menschlichen Leser kaum auffällt.

const und let

Bearbeiten

Ursprünglich kein standardisiertes JavaScript waren die beiden Deklarationen

  • const für konstante Werte, die nicht mehr irrtümlich geändert werden können (Mozilla JS 1.5[4])
  • let für Zuweisungen mit Block scope (Mozilla JS 1.7[5])

Zumindest bis zum IE10 war dies ein Syntaxfehler. Im Entwurf zu ECMA-6 vom April 2012 werden sie hingegen als neue Syntaxelemente gefordert. Gleichwohl wird es einige Jahre dauern, bis alle Benutzer der Wikis entsprechende Skripte ohne Absturz nutzen können. Die Implementierungsdetails beim Firefox unterscheiden sich von ECMA.

Für die meisten Skripte im Wiki werden diese keinen Vorteil bieten. var kann problemlos weiter genutzt werden. Es erwartet eine Deklaration zu Beginn der Funktionseinheit und dort sind alle auftretenden Symbole innerhalb der Funktion übersichtlich zusammengestellt. Unabhängige Nutzung desselben Namens in Blöcken kann auch verwirren. Versehentliche Umdefinition eines als const beabsichtigten Wertes ist bei überschaubaren Skripten kein typischer Fehler.

Globale Variablen und die anderen Benutzer

Bearbeiten

Vorweg: Wenn du nur für dich alleine programmierst – mach, was du willst. Insbesondere, wenn du keine Skripte anderer Benutzer einbindest.

Wenn du dein Skript aber anderen Benutzern zur Verfügung stellst, gar ein projekt- bis weltweites Gadget auf der Seite Helferlein anbieten möchtest – dann solltest du unter den Bedingungen von 2012 bei Neuprogrammierungen oder grundlegenden Überarbeitungen keine globale Variable mehr in die Welt setzen, möglichst auch keine mehr erwarten.

In früheren Zeiten hatten die „Monobooks“ locker 250 globale Variablen kreiert; hinzu kamen einige Dutzend Funktionsdefinitionen. Dazu möglicherweise noch ein Dutzend Importe an Skripten anderer Benutzer, die ebenfalls großzügig globale Variablen verstreuten; mit den wikibits sammelte sich ein freundliches Hundert an Funktionen. – – Nach einigen Jahren war es vielleicht noch dem Skriptautor persönlich möglich, sich durch diesen Dschungel durchzufinden – aber Namenskonflikte[6] sind unvermeidlich, gerade wenn im gleichen Themengebiet ähnliche Aktivitäten entfaltet werden. Mehrere functions und Variablen unterschiedlicher Herkunft können leicht unterschiedliche Bedeutungen haben und zu unentwirrbaren Folgefehlern führen. Sicherheitstechnisch ist das überhaupt nicht mehr zu beherrschen, wenn irgendwo ein schwarzes Schaf eingeschmuggelt wird. Interaktionsprobleme mit neuen Browser-Versionen und einfach nur seltsames, nicht reproduzierbares Fehlverhalten bleibt unaufgeklärt.

Zwei Wege mit ihren Vor- und Nachteilen führen aus dem perfekten Chaos.

Anonyme Funktion

Bearbeiten

In vielen Fällen wird ohnehin eine Funktion als Übergabewert erwartet. Sie kann namenlos als Parameter geschaffen werden:

  jQuery(document).ready(function () {
     var n  =  document.links.length;
     if (window.xyz === ....
     // ....
                                     }   // function ()
  );   // jQuery(document).ready

Nach dem Laden des Dokuments soll die Funktion ausgeführt werden. Genauso nach Bereitstellung eines Moduls:

  mw.loader.using("mediawiki.util",
                  function () {
                     mw.loader.load(mw.config.get("wgServer")
                                    + mw.util.wikiScript()
                                    + "?title="
                                    + mw.util.wikiUrlencode(access)
                                    + "&action=raw&ctype=text/javascript",
                                    "text/javascript");
                  });

Pro Die Funktion trägt keinen Namen, kann also mit keiner anderen gleichen Namens kollidieren.
Pro “Pollution of global namespace” („Verschmutzung des globalen Namensraums“) unterbleibt somit.
Pro Anwender haben keinerlei Zugriff und können die inneren Abläufe nicht manipulieren.
Kontra Die Funktion kann nicht wiederverwendet werden, an keiner anderen Stelle aufgerufen werden.

Zusammengefasst eignet sich das Konzept der anonymen Funktion besonders für folgende Fälle:

  • Der Code wird nur speziell in einer bestimmten Situation gebraucht und kann nie wieder benötigt werden; wird ggf. nur ein einziges Mal ausgeführt.
  • Der Code ist relativ kurz, überschaubar und kann einmalig ausgetestet bereitgestellt werden.
  • Man will bestimmte Funktionalitäten nicht dauerhaft anbieten und unterstützen müssen und will lediglich intern benötigte Eigenschaften vor den Benutzern verstecken, damit man jederzeit die inneren Abläufe verändern kann.

Anmerkung: Es ist möglich, anonyme Funktionen zu Debugging-Zwecken während der Entwicklung mit „Bezeichnern“ zu versehen, unter denen sie im Aufrufstapel leicht zu identifizieren sind. Diese „benannten Funktionsausdrücke“ sind aber nicht kompatibel mit allen Browsern und Versionen der Wikipedia-Benutzer und sollten deshalb nur vorübergehend und für eigene Zwecke benutzt werden; sie lassen sich ja mit /* ... */ auskommentieren.[7]

Anwendungsobjekt

Bearbeiten

Im mw-Objekt wurde im Herbst 2011 eine öffentlich zugängliche Komponente mw.libs eingeführt. Sie solle dazu benutzt werden, um Erweiterungen anzudocken – wie genau man sich dies vorstellt, wurde nicht verlautbart.

Man kann nun ein „Anwendungsobjekt“ für seine Problemlösung definieren. Alle Funktionen und Variablen werden zu Komponenten in diesem Anwendungsobjekt und sind dem Global scope entzogen.

Dockt man sein Anwendungsobjekt nun an mw.libs an, sollte die Ausführung auch bei mehrfachen Threads sicher sein, da das mw-Objekt Komponente von window ist. Zwar besteht hier immer noch die Gefahr von Namenskonflikten mit anderen Anwendungsobjekten gleichen Namens, aber dies ist auf einen Bezeichner pro Anwendung reduziert.

Die Definition erfolgt wie folgt:

 mw.libs.myApplication  =  { };

Man sollte direkt unter mw.libs nur Anwendungen einfügen, die seitens der prinzipiellen Funktionalität und auch durch ihre Namensgebung internationalisierbar sind (deren Name somit auf dem Englischen basiert).

Eher deutschsprachige Anwendungen sollten lieber die Komponente dewiki benutzen:[8][9]

 if (! mw.libs.dewiki) {
    mw.libs.dewiki  =  { };
 }
 mw.libs.dewiki.MeineAnwendung  =  { };

Alle weiteren Einzelfunktionen und Daten werden nun als Komponenten des Anwendungsobjekts strukturiert.


Pro “Pollution of global namespace” („Verschmutzung des globalen Namensraums“) unterbleibt.
Pro Es lässt sich sofort nachvollziehen, welchen Paketen welche Funktion entstammt. Konfigurationsdaten lassen sich ebenfalls leicht und zweifelsfrei der Anwendung zuordnen.
Pro Eine Einzelfunktion kann mit keiner anderen gleichen Namens kollidieren.
Pro Die Funktionen können wiederverwendet, also beliebig aufgerufen werden. Insbesondere eignet sich dies dazu, eine API anzubieten.
Pro Die Funktionen sind im Debugger leicht zu überwachen.
Pro Komplexe Strukturen können aufgebaut werden.
Pro Auch innerhalb eines Anwendungsobjekts sind anonyme und damit unzugängliche Strukturen definierbar.[10]
Kontra Anwender haben Zugriff auf öffentliche Komponenten, könnten die inneren Abläufe manipulieren oder unerwünscht aufrufen.
Kontra Es könnte ein Namenskonflikt mit einem anderen Anwendungsobjekt ähnlicher Funktion auftreten.

Zusammengefasst eignet sich das Konzept des Anwendungsobjekts besonders für folgende Fälle:

  • Der Code soll mehrfach ausgeführt werden können und auch von Benutzern aufgerufen werden.
  • Benutzer sollen Konfigurationsdaten vorgeben können.
  • Der Code ist komplex und umfangreich; es ist mit ständiger Weiterentwicklung zu rechnen.


Eine Benutzerkonfiguration verwendet klassischerweise eine Komponente .options, .opt oder .config – am Beginn der Anwendungsprogrammierung muss mit der Existenz eines zuvor definierten leeren Anwendungsobjekts mit Konfigurationsdaten gerechnet werden:

   if (! mw.libs.myApplication) {
      mw.libs.myApplication  =  { opt: { }
                                };
   }
   if (! mw.libs.myApplication.opt) {
      mw.libs.myApplication.opt  =  { };
   }
   if (typeof mw.libs.myApplication.opt.XYZ !== "string") {
      mw.libs.myApplication.opt.XYZ  =  "default";
   }
   if (typeof mw.libs.myApplication.opt.URL !== "string") {
      mw.libs.myApplication.opt.URL  =  "http://..........";
   }

Wenn keine Validitätsprüfung erfolgen soll, geht dies eleganter mit

   var options  =  { XYZ: "default",
                     URL: "http://.........."
                   };
   if (mw.libs.myApplication.opt) {
      options  =  jQuery.extend(options, mw.libs.myApplication.opt);
   }

Dabei werden die Benutzerdaten opt nicht verändert; die interne Objekt-Komponente options wird mit den Default-Werten vorbelegt. Anschließend enthält options die gewünschten Einstellungen, je nach den Angaben in opt auch gemischt.

Auf Seiten eines Anwenders erfolgt auf Wunsch die analoge Definition von mw.libs.myApplication.opt mit entsprechenden Daten.

Schließlich sollte man in einem komplexen Objekt immer Versionshinweise unterbringen; entweder numerisch (als Dezimalzahl) oder als Zeichenkette; etwa mit Datum und Kurzkommentar:

 mw.libs.dewiki.MeineAnwendung.version  =  "2012-02-25 1.4 debug P.C.";

Benutzerdefinierte Konfigurationsdaten

Bearbeiten

Sollen Benutzer die Möglichkeit bekommen, das Verhalten von Skripten durch Vorgabe von Daten zu steuern, muss zunächst einmal sichergestellt sein, dass das Benutzerskript wie etwa common.js bereits ausgeführt worden ist. Das ist unter den modernen Bedingungen des asynchronen Ladens nicht mehr selbstverständlich.

Das abgeschlossene Laden der Standard-Benutzerskripte wird durch das Modul user signalisiert:

 // Allgemeine Initialisierung der Anwendung
 // ...
 // kann bereits beim Laden erfolgen.
 mw.loader.using( "user", dieseAnwendung.run );

Die Funktion dieseAnwendung.run() kann dann die Benutzerkonfiguration auswerten und unter ihrer Berücksichtigung die eigentlichen Aktivitäten entfalten.

Konfigurationsdaten im Anwendungsobjekt

Bearbeiten

Beim Anwendungsobjekt bietet sich eine Komponente .options (auch .opt oder .config) an.

Benutzerseitig wird ein leeres Anwendungsobjekt vorgegeben und mit den gewünschten Einstellungen gefüllt; falls das Anwendungsobjekt schon existieren sollte, wird es nicht überschrieben.

 if (! mw.libs.dieseAnwendung) {
    mw.libs.dieseAnwendung  =  { opt: { }
                               };
 }
 mw.libs.dieseAnwendung.opt.meinSonderwunsch  =  42;

Die Anwendung selbst prüft beim Laden, ob zuvor ein Anwendungsobjekt definiert war, und wertet bei der eigentlichen Ausführung mögliche Konfigurationsdaten aus.

Benutzeroptionen in den allgemeinen Benutzereinstellungen ablegen

Bearbeiten

Ein für die Anwender komfortabler Weg ist die Speicherung in den Benutzereinstellungen. Das Benutzerskript preferencesGadgetOptions vereinfacht die Handhabung durch Programmierer und bietet den Anwendern ein interaktives Formular an.

Globale Benutzerkonfiguration

Bearbeiten

Gibt es kein Anwendungsobjekt, dann können Benutzerwünsche den Optionen hinzugefügt werden. Damit stehen sie nicht im globalen Namensraum und es ist auch sofort nachzuvollziehen, dass es sich um eine individuelle Einstellung handelt.

Das Modul mediawiki.user ist aber möglicherweise nicht sofort verfügbar, die benutzerseitige Zuweisung lautet deshalb etwas umständlich:

 mw.loader.using( "mediawiki.user",
                  function () {
                     mw.user.options.set("meinSonderwunsch", 42);
                  } );

Auf Seiten der Anwendung müsste die oben gegebene Bedingung erweitert werden:

 mw.loader.using( [ "user", "mediawiki.user", "user.options" ],
                  Anwendung.run );

In der Funktion Anwendung.run() kann mit

 var udo  =  mw.user.options.get("meinSonderwunsch", false);

der aktuelle Wert abgefragt werden.

Globale Konfigurationseinstellungen

Bearbeiten

Eine elegante Möglichkeit, Konfigurationsdaten der Anwendung außerhalb der globalen Variablen unterzubringen, wenn kein Anwendungsobjekt vorhanden ist, wäre die Erweiterung der Systemnachrichten. Das gilt insbesondere für sprachabhängige Textelemente, wenn eines Tages ein international nutzbares Gadget auch von translatewiki unterstützt werden soll.

 switch ( mw.config.get( "wgUserLanguage" ) ) {
    case "de":
    case "de-at":
    case "de-ch":
    case "de-formal":
       mw.messages.set( {
             "myTextBBshort": "Textbrösel",
             "myTextBBlong":  "lange Geschichte"
                        } );
       break;
    case "en":
    default:
       mw.messages.set( {
             "myTextBBshort": "text snippet",
             "myTextBBlong":  "longer story"
                        } );
       break;
 };   // switch wgUserLanguage

Später ist dies wie jede (für JavaScript registrierte) Systemnachricht zu nutzen über:

 var s  =  mw.messages.get( "myTextBBshort" );

Good practice

Bearbeiten

Beabsichtigte globale Variablen und Funktionen sollten in der immer komplexeren Welt der Wiki-Skripte nur noch mit Bezugnahme auf ein Objekt verwendet werden. Damit wird zum einen die sichere Wirkung gewährleistet, zum anderen wird die beabsichtigte Funktion auf Anhieb und zweifelsfrei verstanden und es kann systematisch analysiert werden, etwa durch Suche nach Zeichenketten.

Geeignete Objekte sind namentlich:

Damit lässt sich umgekehrt schließen: Ist kein Objekt-Bezeichner vorangestellt, dann handelt es sich um eine lokale Variable einer Funktion, die auch mit var deklariert worden sein sollte.

Es ist zu erwarten, dass eines Tages die Benutzerskripte gekapselt werden.[11] Das bedeutet, mit var deklarierte Variablen stehen dann nur noch innerhalb der Standard-Benutzerskripte zur Verfügung und haben keine Auswirkung mehr auf andere Skripte. Die Kommunikation sollte über geeignete Komponenten von mw.libs geleitet werden, um nicht das globale Objekt window genauso unstrukturiert zu fluten wie zuvor.

Undeklarierte Variablen

Bearbeiten

In jeder professionellen Programmierpraxis gelten undeklarierte Variablen als Fehler. JavaScript lässt das zwar zu – bedingt durch die Vorgeschichte Mitte der 1990er, als es um ein Dutzend Zeilen ging, die in eine HTML-Seite eingebettet wurden. Gleichwohl sollten alle Variablen explizit deklariert sein, im aktuellen Skript oder in einem zuvor geladenen (MediaWiki). In der komplexen Situation einer mit allerlei Gadgets verfeinerten Wiki-Seite sind Programmierfehler durch vertippte Variablennamen sonst nicht aufzuspüren, auch nicht mit Entwicklerwerkzeugen.

Initialisierung

Bearbeiten

Unabhängig von der Deklaration mit var steht die Frage der korrekten Initialisierung; wenn es einen an dieser Stelle sinnvollen Startwert gibt, dann sollte dieser zu Beginn und im var explizit gesetzt werden. (Eine for-Schleife wird im Zusammenhang mit Zahlenwerten belegt.)

Undeklariert und undefined

Bearbeiten

Alle Symbole gelten als „deklariert“, die

  • mit var in der Funktion oder global vereinbart wurden,
  • Argument einer übergeordneten Funktion waren,
  • durch catch gesetzt wurden,
  • Eigenschaft eines Objekts sind (und damit jedes Element eines Array).

Daraus ergibt sich:

  • Wenn sichergestellt ist, dass eine Variable (oder Eigenschaft eines Objekts) deklariert ist, kann mit x !== undefined der Status geprüft werden, also ob ein Wert beim Funktionsaufruf oder sonst zugewiesen wurde (Initialisierung?).
  • Wenn nicht sichergestellt ist, ob die Variable bereits grundsätzlich existiert, ist zur Vermeidung von Laufzeitfehlern mit typeof x abzuprüfen. Der Operator bzw. die Funktion typeof ist die einzige, mit der ein undeklariertes Symbol in Verbindung gebracht werden darf. Das ist nicht ganz so schnell wie der Vergleich mit undefined. Es eignet sich aber besonders, um herauszufinden, ob in anderen Skripten eine entsprechende Variable vorgegeben wurde. Außerdem lässt sich gleich prüfen, ob auch der erwartete Datentyp vorliegt. Spricht man jedoch globale Variablen als Eigenschaften des window-Objekts an, lassen sie sich direkt mit undefined vergleichen.

Mit this kann der aktuelle Kontext festgestellt werden.


Außerhalb von Funktionen (im global scope) verweist this auf das “global object”. In einem Web-Browser sollte window das “global object” sein, im Regelfall ist also this===window (siehe aber stack scope). Deshalb sollte in Wiki-Projekten hier explizit window benutzt werden.

Der mit "use strict"; eingeführte modernere Modus definiert das übergeordnete Objekt unterschiedlich:

 function free1 () {
    return this;
 }
 function free2 () {
    "use strict";
    return this;
 }
 window.free3 = function () {
    "use strict";
    return this;
 };

Rückgabewerte bei korrekter Implementierung im Browser:

  1. window
  2. undefined
  3. window


Innerhalb einer Funktion ist this im Regelfall der Aufrufkontext und damit – anders als in anderen Programmiersprachen – nicht immer die Vererbung zur Zeit der Definition.

Ist eine Funktion Komponente eines Objekts, verweist this zunächst auf dieses Objekt:

  var obj = { prop: 42 };
  obj.fun = function () {
     alert(obj.prop);
     alert(this.prop);
  };
  obj.fun();

In beiden Fällen wird 42 ausgegeben.

Das hilft insbesondere bei Bibliotheksfunktionen und Anwendungsobjekten, da das nächstübergeordnete Objekt geerbt werden kann und andere Variablen und Funktionen nicht aktualisiert werden müssen, wenn sich etwas an der höheren Objektstruktur ändert. Bei mittels .prototype definierten Klassenobjekten verweist this auf die einzelne Instanz bei Definition. – Allerdings ist damit auch das Ende der Vererbung in JavaScript erreicht.

Dies hat jedoch einige Tücken, wenn die Funktion als “event handler” benutzt wird. In diesem Fall wird this das auslösende Element. Wird die Funktion etwa in einem jQuery-Aufruf angegeben, so wird hier der Funktionsrumpf in einer jQuery-eigenen Struktur gespeichert. Aus

 jQuery(document).ready(obj.fun);

wird intern etwas wie

 jQuery17108507570563352369 = function () {
    alert(obj.prop);
    alert(this.prop);
 };

Wenn dann das Ereignis document ready eintritt, wird aufgerufen:

 jQuery17108507570563352369();

Hier greift this.prop ins Leere; die erste Meldung ist erfolgreich, die zweite liefert undefined. Inhalt von this ist das document Standardobjekt.

Genauso wurden viele Jahre hindurch eigene Instanzen für das “global object” benutzt, wenn ein zeitgebundenes Ereignis mit setTimeout() usw. gestartet wird.

Werden die Elemente einer Suchabfrage mittels jQuery mit .each() analysiert, liefert this das jeweilige DOM-Element.

Um in einem Objekt fehlersicher und eindeutig immer auf sich selbst zuzugreifen, wird häufig vom Konstruktor aus das in anderen Programmiersprachen etwas seltsame Konstrukt definiert:

 var that = this;

Die Variable that wird dann lokal im Objekt und beschreibt unabhängig vom Aufrufkontext das Objekt selbst.


Mit den Standardmethoden .call() und .apply() sowie neuerdings .bind() kann die Zuweisung in Funktionsobjekten beeinflusst werden.

In den folgenden Beispielen ist fun eine Funktion.

  • fun.apply(thisArg[, argsArray])
  • fun.call(''thisArg''[, arg1[, arg2[, [...]]])

Die Funktion fun wird ausgeführt; innerhalb von fun bekommt this den Wert thisArg, wenn dies ein Objekt ist; ansonsten wird ein analoges Objekt gebildet. Die normale Zuordnung von this als Aufrufkontext wird hier aufgehoben.

  • Der Unterschied zwischen .apply und .call liegt lediglich im Format der Argumentliste.

Relativ neu ist

  • fun.bind(''thisArg''[, arg1[, arg2[, [...]]])

Als Ergebnis wird eine neue Funktion gebildet, die den gleichen Inhalt hat wie fun, aber ein spezifiziertes this und die weiteren Argumente voranstellt.

Die Abfrage auf Existenz und Verwendbarkeit eines Datenelements verleitet gelegentlich zu Trugschlüssen. Was ein „leerer Wert“ wäre, ist mitunter nicht ganz so trivial wie das klassische Paar true/false.

 var a  =  [ ];
 var n  =  null;
 var o  =  { };
 var s  =  "";
 var u  =  undefined;
 var v;
 var z  =  0;
if (a) {
    window.alert("a: WAHR="+a);
 } else {
    window.alert("a: FALSCH="+a);
 }
 if (n) {
    window.alert("n: WAHR="+n);
 } else {
    window.alert("n: FALSCH="+n);
 }
 if (o) {
    window.alert("o: WAHR="+o);
 } else {
    window.alert("o: FALSCH="+o);
 }
 if (s) {
    window.alert("s: WAHR="+s);
 } else {
    window.alert("s: FALSCH="+s);
 }
 if (u) {
    window.alert("u: WAHR="+u);
 } else {
    window.alert("u: FALSCH="+u);
 }
 if (v) {
    window.alert("v: WAHR="+v);
 } else {
    window.alert("v: FALSCH="+v);
 }
 if (x) {
    window.alert("x: WAHR="+x);
 } else {
    window.alert("x: FALSCH="+x);
 }
 if (z) {
    window.alert("z: WAHR="+z);
 } else {
    window.alert("z: FALSCH="+z);
 }

ergibt

      a: WAHR=
      n: FALSCH=null
      o: WAHR=[object Object]
      s: FALSCH=
      u: FALSCH=undefined
      v: FALSCH=undefined
      **** x is not defined **** Syntaxfehler
      z: FALSCH=0
  • Object: Um sicher auf eine Komponente eines von außen kommenden mutmaßlichen Objekts zugreifen zu können, müssen zwei Bedingungen erfüllt sein:
    1. typeof o === "object"
    2. Nicht null
    Üblicherweise wird das in der Abfrage zusammengefasst:
    if (o && typeof o === "object") {
    Nur dann lässt sich ohne Laufzeitfehler o.x auswerten.[12]
  • String: In JavaScript ist die leere Zeichenkette Nichts. Das ist anders als in anderen Programmiersprachen, wo jede Zeichenkette ein allokiertes Objekt darstellt, selbst wenn der Inhalt die Länge Null hat.
  • Array: Im Gegensatz dazu ist ein leeres Array allokiert und liefert Nicht-Null, wie auch das Objekt ohne eigene Elemente.
  • Die Funktion jquery.isEmpty() im Modul jquery.mwExtension stellt auch leeres Array [] oder Objekt {} fest, nebst Basistypen.
  • undefined ./. null
    Zwei Fälle sind zu unterscheiden:
    1. Strikter oder typgebundener Vergleich greift nur bei exakter Übereinstimmung:
      • x === undefined
      • x === null
    2. Einfacher Vergleich mit Typkonversion
      • x == undefined
        entspricht
        (x === undefined || x === null)

Siehe auch

Bearbeiten

Anmerkungen

Bearbeiten
  1. siehe mw:Manual:Coding conventions/JavaScript #Closures
  2. ECMA-262/5: 15.1 “The Global Object”; 10.3, “execution context”
  3. Siehe: crockford.com (englisch)
  4. MDN
  5. MDN
  6. msg“ oder das Array ta[] in wikibits
  7. Named function expressions demystified (englisch)
  8. a b Sofern eines Tages definiert:
     mw.libs.dewiki  =  { };
     window.dewiki   =  mw.libs.dewiki;
    

    Spezifische Datenelemente der deutschsprachigen Wikipedia ohne weltweite Unterstützung.

  9. Mit gleicher Wirkung auf kryptisch formuiert:
     mw.libs.dewiki = mw.libs.dewiki || {};
    
  10. Siehe beispielsweise die Programmierung von mw.loader oder das mw-Objekt selbst.
  11. phab:T65728 (Bugzilla:63728)
  12. In JavaScript werden die Ausdrücke von links nach rechts abgearbeitet; die &&-Verknüpfung bricht ab, sobald ein Wert falsch angetroffen wird. So sollte es zumindest sein.
    • Wäre zu befürchten, dass o undeklariert sein kann, muss zuerst über   typeof o === "object"   gesichert werden, dass o existiert, bevor o (auf Nicht-null) geprüft werden kann.
    • Ist etwa bei einer Komponente oder lokal in einer Funktion gesichert, dass o immer deklariert ist, kostet die Abfrage o (also Nicht-null) weniger und würde im Fall von undefined oder null die leicht aufwändigere typeof-Prüfung ersparen.