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
BearbeitenEine Variable kann auf eine von drei Arten entstehen:
- Ein bisher unbenutzter Variablenname wird verwendet. (mehr dazu)
- Eine Deklaration mit var.
- 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.
- In den Browsern wird die so entstandene Variable unter dem Funktionsnamen meist so behandelt, als ob sie mit
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.
Scopes
BearbeitenDer Scope ist der Gültigkeitsbereich, in dem eine Variable bekannt ist und ihr Wert gesetzt und verwendet werden kann.
Global scope
BearbeitenZum „globalen Gültigkeitsbereich“ gehört:
- Jede Variable, die nicht innerhalb einer Funktion mit var deklariert wird oder Argument ist.
- 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
BearbeitenIn 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
BearbeitenVorweg: 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
BearbeitenImmer 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()
oderwindow.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
BearbeitenJavaScript 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
BearbeitenBei 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
oderjQuery
ü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 auchjQuery
sind eine Eigenschaft des Objekteswindow
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
BearbeitenDie 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
BearbeitenEgal, 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
BearbeitenMW 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
BearbeitenJavaScript 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
BearbeitenVorweg: 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
BearbeitenIn 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 “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
BearbeitenIm 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
BearbeitenSollen 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.
- Wird die Anwendung erst mittels
mw.loader.load()
durch den Benutzer geladen, dann ist diese Vorbedingung automatisch erfüllt. - Bei Gadgets oder in projektweiten Standardressourcen wie MediaWiki:Common.js ist das nicht zwingend der Fall.
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
BearbeitenBeim 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
BearbeitenEin 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
BearbeitenGibt 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
BearbeitenEine 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
BearbeitenBeabsichtigte 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:
window
document
- mw
- jQuery bzw.
$
- Anwendungsobjekt – sofern nicht ohnehin schon Komponente unter
mw.libs
dewiki
[8]
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
BearbeitenIn 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
BearbeitenUnabhä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 Funktiontypeof
ist die einzige, mit der ein undeklariertes Symbol in Verbindung gebracht werden darf. Das ist nicht ganz so schnell wie der Vergleich mitundefined
. 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 deswindow
-Objekts an, lassen sie sich direkt mitundefined
vergleichen.
this
BearbeitenMit 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:
window
undefined
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.
Nichts
BearbeitenDie 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:
typeof o === "object"
- Nicht
null
- Üblicherweise wird das in der Abfrage zusammengefasst:
if (o && typeof o === "object") {
Nur dann lässt sich ohne Laufzeitfehlero.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:- Strikter oder typgebundener Vergleich greift nur bei exakter Übereinstimmung:
x === undefined
x === null
- Einfacher Vergleich mit Typkonversion
x == undefined
entspricht(x === undefined || x === null)
- Strikter oder typgebundener Vergleich greift nur bei exakter Übereinstimmung:
Siehe auch
BearbeitenAnmerkungen
Bearbeiten- ↑ siehe mw:Manual:Coding conventions/JavaScript #Closures
- ↑ ECMA-262/5: 15.1 “The Global Object”; 10.3, “execution context”
- ↑ Siehe: crockford.com (englisch)
- ↑ MDN
- ↑ MDN
- ↑ „msg“ oder das Array
ta[]
in wikibits - ↑ Named function expressions demystified (englisch)
- ↑ a b
Sofern eines Tages definiert:
mw.libs.dewiki = { }; window.dewiki = mw.libs.dewiki;
Spezifische Datenelemente der deutschsprachigen Wikipedia ohne weltweite Unterstützung.
- ↑
Mit gleicher Wirkung auf kryptisch formuiert:
mw.libs.dewiki = mw.libs.dewiki || {};
- ↑ Siehe beispielsweise die Programmierung von
mw.loader
oder dasmw
-Objekt selbst. - ↑ phab:T65728 (Bugzilla:63728)
- ↑
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 übertypeof o === "object"
gesichert werden, dasso
existiert, bevoro
(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 Abfrageo
(also Nicht-null
) weniger und würde im Fall vonundefined
odernull
die leicht aufwändigeretypeof
-Prüfung ersparen.
- Wäre zu befürchten, dass