Verschlüsselung von RAIDs

Will man einen RAID verschlüsseln, steht man vor verschiedenen Problemen. Zuallererst muss man sich klar werden, dass eine Verschlüsselung die Datensicherheit (Redundanz) gefährden kann. Tausend Backups nützen nichts wenn der Schlüssel bzw. das Schlüsselfile verloren gegangen ist. Das klingt banal, schießt einen aber ins Knie, wenn der RAID bei einem Systemausfall die wichtigen Daten am Leben hält, das Schlüsselfile aber mit ins Nirwana gegangen ist.

Umgekehrt torpediert dein RAID meist mit der schieren Masse an  Daten die Datensicherheit im Sinne des Zugriffsschutzes. Je mehr Daten man mit dem gleichen Schlüssel verschlüsselt, desto „leichter“ lässt sich der Schlüssel aus dieser Menge extrahieren. Ab 2 GByte sollte man sich intensiv damit beschäftigen, welchen Verschlüsselungsalgorithmus (Cipher) man verwenden kann und mit welcher Schlüssellänge man arbeiten sollte.

Neben solchen theoretischen Vorüberlegungen muss man sich aber auch klar werden wie man Verschlüsseln will. Welche Features will man nutzen, worauf kann man verzichten. Ich für meinen Teil hatte klare Vorstellungen von meinem Setup:

  • FullDiskEncryption (FDE): Das RAID Array soll im ganzen verschlüsselt werden.
  • Dynamische RAID-Vergrößerung: Ab gewissen Speichergrößen ist eine Verdopplung des Speichers nicht mehr praktikabel oder schlicht bezahlbar.

Beide Punkte zusammen haben jedoch ihren Knackpunkt. Nicht alle Verschlüsselungstechnologien sind bei FDE (oder überhaupt) in der Lage einmal verschlüsselte Container/Volumes in der Größe zu verändern. TrueCrypt kann dies nur bei Containern und dann mit einem Performance-Overhead, der inakzeptabel ist. Bei den OpenSource-Technologien bleibt dann nur noch dm-crypt über. Dieses hat jedoch die „Schwäche“, dass der Verschlüsselungheader (welcher Cipher, Start, Ende, etc) selber unverschlüsselt auf der Platte liegt. Sicherheitstechnisch ist das kein Problem. Auch wenn der Angreifer den Cipher kennt, beißt er sich bei den richtigen Algorithmen und Schlüssellängen die Zähne aus. Nur kann ein dm-crypt Benutzer nicht glaubhaft abstreiten, dass er eben dm-crypt nicht benutzt.

Mir war die juristische Debatte erstmal egal, ich wollte ein verschlüsseltes dynamisches RAID-Device. Das hat mich ein ganzes Wochenende gekostet (500GB auf 750GB zu migrieren dauert immer ungefähr 4 Stunden). Es hat sich mir ein zentrales Problem in den weg gestellt. Es gibt für die Konsole kein Tool, dass eine Partition vergrößern kann, dessen Dateisystem es nicht erkennt. Man kann mittels fdisk die Partitionstabelle löschen und neu schreiben. So sadomasochistisch bin ich aber nicht veranlagt. Man riskiert immer vollen Datenverlust!

Man kann den Umweg über Logical Volume Manager (LVM) gehen. Dazu wird bei einem vergrößerten Device nicht die Partition vergrößert, sondern im neuen freien Bereich einfach eine weitere Partition erstellt. Diese wird dann dem Logischen Device hinzugefügt. Arbeitet man nur mit einem Fake- oder Software-RAID, mag das akzeptabel sein. Kommt es bei diesen zu einem Stromausfall darf man eh beten. Hardware-RAIDs nutzen jedoch BBUs um Datenverlust im Fehlerfall zu unterbinden. Was mit der LVM Zwischenschicht wieder ausgehebelt währe.

Möglichkeit drei ist einfach: man nutzt keine Partitionierung. Dazu muss man einfach wie folgt sein Device „beschreiben“

sudo parted /dev/sdX
mklabel msdos
quit

Nach dieser Aktion hat man eine MSDOS – Partitionierung, aber nicht erschrecken, die verschwindet gleich wieder ;).
Jetzt kann man die Festplatte direkt verschlüsseln, was z.B. bei einer GPT – Partitionstabelle nicht geht.

sudo cryptsetup luksFormat --cipher aes-xts-plain:sha256 -s 256 -q /dev/sdX #Verschlüsselung anlegen
sudo cryptsetup luksOpen /dev/sdX someCryptDev #Verschlüsseltes Device öffnen
mkfs.ext3 /dev/mapper/someCryptDev #verschlüsseltes Device formatieren
mount /dev/mapper/someCryptDev /mnt/someCryptDevUncrypted

Beim Wiedereinhängen einfach luksFormat und mkfs weglassen, sonst blöd 😉
Interessant wird jetzt die Vergrößerung. Dazu im unter lagerten RAID erstmal das Device vergrößern. Um die neue Festplattengröße dem System bekannt zu machen muss man entweder mittels des RAID-Treibers ein rescann auslösen, man entlädt einfach den ganzen Treiber und hängt ihn wieder ein oder startet einfach neu.

sudo lsmod #alle Treiber anzeigen lassen und den RAID-Treiber raus suchen.
sudo modprobe -r
 #RAID-Treiber entladen
sudo modprobe
 #RAID-Treiber laden

Letztes geht nicht ohne das aushängen der gemountet Partition. Besser gesagt, es geht schon, bloß muss man dann mit Datenverlust rechnen. Ein Rescan sollte zu keinem Datenverlust führen, das ist jedoch Treiber-abhängig, in jedem Fall das Manual oder den Maintainer konsultieren. Für 3Ware (jetzt LSI) Raids müssen die Devices z.B. ausgehängt sein.

Ist die neue Festplattengröße im System bekannt, muss man sie nutzbar machen. Dazu gibt es zwei Möglichkeiten:

  • Online – ohne Downtime des Dateisystems: Dies benötigt ein Dateisystem, was das Vergrößern/Verkleinern „on-the-Fly“ unterstützt. Das können z.b. EXT3 oder XFS.
    sudo fdisk -lu /dev/sdX #Sektoren raus schreiben
    sudo cryptsetup status  #Offset raus schreiben
    sudo cryptsetup resize -o  -b ;
    sudo resize2fs /dev/mapper/sdX #resize des FileSystems am Beispiel EXT3
  • Offline – mit Downtime des Dateisystems: Die kann mit allen Dateisystemen durchgeführt werden, die vergrößert/verkleinert werden können. Es ist auch ein Stück komfortabler.
    sudo umount /dev/mapper/; #aushängen des verschlüsselten Devices (wenn nicht schon vor dem Scann passiert)
    sudo cryptsetup luksClose  #schließen des verschlüsselten Devices (wenn nicht schon vor dem Scann passiert)
    sudo cryptsetup luksOpen /dev/sdX  #damit ist auch schon die Vergrößerung des verschlüsselten Devise erledigt...
    sudo resize2fs /dev/mapper/ #resize des FileSystems am Beispiel EXT3/EXT2

    Beim öffnen des Device nutzt selbiges scheinbar automatisch allen verfügbaren Platz, wenn man nichts anderes (mittels resize) einstellt.

So kann kann in jedem Fall ohne viel Stress seine RAID-Device stückchenweise nach seinen Bedürfnissen erweitern. Dennoch sollte man von allen wichtigen Daten immer ein Backup haben! Zudem sollte man dieses Vorgehen ein, zwei mal geübt haben, bevor man es mit wichtigen Daten durchführt;)

3Ware installation

Das erste was nach der Installation der Hardware auffällt ist, dass der Boot-Vorgang extrem viel länger dauert. Beim ersten Start hat es locker 30 Sek gebraucht, bis das BIOS des RAID-Controllers durch war und mein Server endlich ins Linux gebootet hat. In den folgenden Boots wird das nicht viel besser.

Die Installation des 9650 ist unter Ubuntu 9.04 denkbar einfach. Auch alle anderen Distributionen werden (wenn auch nicht offiziell) ohne Probleme unterstützt. Einzig die Kernelversion 2.6.14  oder die entsprechenden Treibermodule werden vorausgesetzt. 3Ware bietet drei Möglichkeiten den RAID-Controller zu administrieren. BIOS, CLI und die 3DM -genannte webbasierte RemoteManagement – Konsole. Die Installation erfolgt problemlos, einzig eine „echte“ JavaRuntime und das Programm „bc“ werden benötigt. Beide sind aber im offiziellen Repository enthalten und man gefährdet seinen Server nicht mit Fremdquellen. Ein „kleines“ „aptitude install“ vorneweg und die Installation kann beginnen.

Hat man eine grafische Oberfläche kann man das Setup einfach so starten, steht einem nur ein Kommandozeilen-Terminal zur Verfügung muss man noch den Parameter „-console“ anhängen. Anschließend führt ein Assistent durch die Installation und nach „wenigen“ Minuten steht einem der RAID-Controller in vollen Funktionsumfang zur Verfügung.

sudo aptitute install bc
tar xfvz 3DM2_CLI-Linux-x86_64-9.5.2.tgz
sudo ./setupLinux_x64.bin -consol

Jetzt wird man durch den Assistenten geführt. Das dauert wie gesagt ein paar Minuten. Anschließend ist alles nach Wunsch installiert und konfiguriert. Will man nachträglich etwas ändern so findet man das Config-File unter /etc/3dm2/

Man muss nur noch dafür sorgen, dass die Remote – Konsole auch automatisch gestartet wird. Leider ist der mitgelieferte Startscript, der auch brav unter /etc/init.d abgelegt wird, nicht ganz Standardkonform. Es fehlen die Angaben zu Required-Start und Required-Stop. Ergo schnell die Datei mit einem Editor der Wahl geöffnet und den Header angepasst.

#!/bin/sh
#
# 3dm2:         Starts the 3ware daemon
#
# Author:       Michael Benz

#
# Default-Start: 3 4 5
# Default-Stop: S 0 1 6
# Required-Start:  $network $remote_fs $syslog
# Required-Stop:   $network $remote_fs $syslog
# Provides: tdm2
# Short-Description: 3ware Daemon
# Description: Start the 3dm2 application which logs the current state
#              of the 3ware DiskSwitch controller card, and then polls
#              for state changes.
#
# config: /etc/3dm2/3dm2.conf

Zeile 9 und 10 sind von mir eingefügt. Anschließend folgenden Befehl ausführen.

sudo update-rc.d tdm2 defaults

Nun startet die Remote-Konsole automatisch beim Systemstart mit.
Für den ersten Test startet man entweder neu oder ruft den Script manuell auf.

/etc/init.d/tdm2 start

Wenn man die Konsole aufrufen will muss man beachten, dass nur HTTPS anfragen beantwortet werden. Nach dem Login (Standartpassword: 3ware) sollte man sofort die Passwörter ändern und ein BIOS-Update einspielen. letzteres bedarf leider eines Neustarts.

Anschließend kann man seine RAID-Arrays konfigurieren.

Ein neues Spielzeug: 3Ware 9650SE

Ich hab es also getan. Ich hab mir einen 3Ware 9650SE zugelegt. Ok wer nach schaut wird feststellen, dass es mehrere Controller mit der Bezeichnung gibt. Ich hab mir den mit 8 Ports gegönnt. Was hat mich nun bewogen so ein „Monster“ für den privat Gebrauch zu kaufen, zumal die Kosten horrend sind. Auf diese Frage gibt es nur eine Antwort: Der Umfang der von solchen Profie-RAIDs geboten wird. An oberster Stelle stehen natürlich Features die die Datensicherheit garantieren. Neben dem obligatorischen RAID 6 bietet der RAID-Controller folgendende Eigenschaften:

  • Festplatten die einen lokalen Fehler melden (Schreib/Lesefehler etc) werden nicht sofort als „unbrauchbar“ betrachtet. Sie steht auch weiterhin als Redundanz zur Verfügung. So kann ein Rebuild schneller erfolgen.
  • Der Schreib/Lese-Cache der Festplatten wird abgeschaltet und der Controller-eigene Cache genutzt. Dieser ist über eine Batterie (Battery Backup Unit – BBU) gesichert. Das garantiert, dass selbst bei einem totalen Systemausfall keine Daten verloren gehen.

Daneben bestechen Performance-Features:

  • Wird nicht der ganze verfügbare Speicherplatz einer RAID-Unit genutzt hinterlegt der RAID-Controler mehr Informationen auf den einzelnen Platten um im falle eines Rebuilds schneller wieder volle Redundanz herzustellen.
  • Zusätzlich werden alle Möglichkeiten genutzt den Plattenzugriff zu optimieren (Queuing,Read/Write-Cache)

Über die grundlegende Performance des Controllers braucht man nicht viel sagen. Wie man es erwartet, ist diese durch die Bank weg hoch. In den verfügbaren Benchmarks liegt er im oberen Drittel.

Neben diesen eckdaten gibt es noch eine Komfortfunktionen die sogar für den Privatgebrauch sehr angenehm sind.

  • Der RAID-Controller kann On-The-Fly RAID-Units vergrößern, migrieren, optimieren. Braucht man mehr Speicherplatz, steckt man einfach eine neue Platte rein oder ersetzt eine Festplatte nach der anderen mit einer Größeren.
  • Umfassenden Remote-Management: Alle funktionen des RAIDs können per Web-Interface aus der Ferne gesteuert werden. Fehler oder Warnung werden per Mail an eine Wartungsadresse. Falls wirklich mal eine Platte ausfällt, bekommt man es auf Wunsch sofort mit.

Alles in allem ein sehr angenehmes Gefühl mit so einem Gerät zu arbeiten.

Netzwerkdurchsatz unter Linux testen

Sollte man mal in die Verlegenheit kommen, die Netzwerkperformance (Durchsatz und co) testen zu müssen, so liefert das CommandLine Tool Netperf gute Dienste. Unter Ubuntu kann man es mittels folgendem Befehl ohne weiteres installieren.

sudo aptitude install netperf

auf einem rechner startet man den Performance-Server.

netserver -4 -p 

Das „-4“ sorgt dafür, dass nur auf IPV4 anfragen reagiert wird. Natürlich geht auch ein V6 für IP6 oder ganz weglassen für beides…

Auf der clientseite muss man nur noch den Test starten

netperf -4 -p  -H 

schon hat man schön übersichtlich die Leistung (s)eines Netzwerkes im Blick

Erster Preview des GpsGallery fertig

Die Probleme mit dem Firefox erwiesen sich als systematisch. Aus irgendeinem Grund parsen Opera und IE folgenden Code anders als der Firefox:

<html>
<div id="someName" />
<script type="text/javascript">
alert(someName);
</script>
</html>

Unter IE/Opera erscheint (wenn auch wenig sinnvoller) Text, da sie automatisch die den Div-Container mit der ID someName in die Variable someName umsetzten. Der FireFox macht das nicht, dort schreit einen einfach nur das „undefined“ an.

Nachdem ich schnell meine Scripte entsprechend angepasst habe, gebe ich das erste mal meinen Code raus, diesmal noch unversioniert. Da ich noch im „ich such mir mein optimales Versionierungstool“-Prozess bin…
Sourcecodes

SqlDependencys feuern nicht, obwohl sie korrekt initialisiert sind.

Es kann vorkommen, dass eine SqlDependency nicht gefeuert wird obwohl sie „korrekt“ initialisiert wurde und es änderungen auf der Datenbank gibt. Der Grund ist, dass auf seiten des MsSql-Servers fehler auftrahten, die nicht zum Client weiter gereicht werden. Über Sinn oder Unsinn kann man sich trefflich streiten, beheben muss man den Fehler jedoch 😉

Häuffigste Ursache eines solchen Fehlers ist das Einspielen eines (Produktiv-) Backups  in die Testumgebung. Dabei wird der DB-Owner nicht auf den User gesetzt der das Backup einspielt (es gibt dafür keine Option) und es wird auch nicht geprüft ob der DB-Owner existiert. Ist letzteres nicht der Fall, wird beim Anlegen einer Dependency versucht auf selbigen zu wechseln… mit folgendem Resultat:

„Cannot execute as the database principal because the principal „dbo“ does not exist, this type of principal cannot be impersonated, or you do not have permission.“

Dieser äußerst unschöne Umstand lässt sich dann meistens im Windows-EventLog nachlesen. Leider gibt es keine anderen Hinweise. Ist der Fehler einmal erkannt, lässt er sich leicht durch folgendes Statement beheben

USE targetDB
GO
sp_changedbowner 'targetUser'

Quelle:
Support-Microsoft

Erste Bugfixes beim GalleryTest

Nach dem „Release“ meines GalleryTest erschlugen mich gleich zwei Fehler von denen ich dachte, dass ich sie „nicht hätte“.

Während unter die Seite unter Localhost einwandfrei läuft, gab es Probleme wenn sie auf meinem WebServer lief.

Das erste Problem war einfach zu lösen. Unter  Opera scheint das AutoSuggest unter localhost abgeschaltet zu sein. Sobald die Seite „real“ gehostet zu werden, fuscht dieses AutoSuggest einem dazwischen. Denn es fängt die KeyUp-Events ab.  Einfach das entsprechende Attribut setzten und das Problem ist gelöst.

 <input type="text" autocomplete="off" />

Das interessantere Problem brachte mir jedoch (wär hätte es anders erwartet) der IE (v8) ein. Ich hab schon damit gerechnet das der IE und der Rest der Browser-Welt Unterschiede herausstellen. Das der IE aber CSS unterschiedlich parsed in Abhängigkeit wo die Seite aufgerufen wird, dass war mir neu.

Stein des Anstoßes war die SuggestBox. Dieser „ausgeblendete“ Div-Container sollte sich mittels z-Index über alle anderen Container legen. Das funktionierte auch so weit ganz gut, bis ich die Webseite im IE öffnete und auch nur wenn sie nicht unter localhost lief.

Das Problem scheint zu sein, dass der GoogleMaps-Container in der Hauptebene liegt, und die SuggestBox in einen anderen Container geschachtelt ist. Diesem Container ist jedoch keine „Tiefe“ zugewiesen. Nachdem ich das geändert hatte, lief die Seite auch unter dem IE einwandfrei.

Interessanter ist, dass der Firefox meine inline-Scriptes komplett ignoriert. Da werd ich nochmal hirnschmalz reinstecken…

Erster Testlauf meiner „Spielwiese“

Wie angekündigt sind meine Arbeiten an einer Galerie mit GPS-Daten so weit fortgeschritten, dass man was zeigen. Das ganze ist noch nicht perfekt. Es gibt noch einige Fehler und Performance-Lecks aber im Großen und Ganzen tut es was es soll.

Bekannte Fehler:

  • Die Suggest-Funktion scheint noch nicht unter allen Browsern so zu laufen wie sie soll…
  • Die Map-Box (GoogleMaps) beim anklicken eines Markers sieht scheiße aus, das wird noch überarbeitet
  • Das Design ansich wird noch überarbeitet.
  • Noch keine Interaktion zwischen Karte und Slider am unteren Bildrand

zu finden ist das Ganze unter:  http://raptor2101.dyndns.org/GalleryTest

Brainfuck Reloaded: OOP in JS

Objektorientiertes Programmieren ist in JS für gestandene OOPler etwas „komplizierter“. Das Member-Methoden ihren Bezug zum Objekt verlieren können ist etwas gewöhnungsbedürftig. Auch sonst ist das Thema der Sichtbarkeiten unter JS etwas gewöhnungsbedürftig. Man hat nun zwei Möglichkeiten:

  • Lernen durch Schmerzen – Trial and Error: das kann für eingefleischte OOP-ler sehr schmerzvoll werden
  • Sich über die Doku einarbeiten

Leider wird letzteres sehr schwer. Die mir bekannten Dokumentation schwanken stark zwischen „völlig belanglos“, „für Anfänger“ bis „am Thema vorbei“. Es gibt nur wenige Dokumente die das Konzept hinter JS klar darlegen. Manch ein Kritiker unterstellt das es so was bei JS nicht gibt 😉

Am besten stellt sich das OOP-Konzept von JS bei einem Vergleich dar. HIer eine Klasse im klassischen OOP alla Java, C++ oder C-Sharp

public class MyClass
{
  private string someVariable;
  
  public class MyClass(string someVariable)
  {
    this.someVariable=someVariable;
  }

  public string GetVariable()
  {
    return someVariable;
  }
}

Nun die Klasse in JS

function MyClass(someVariable){
  this.someVariable=someVariable;
}
MyClass.prototype.AlertVariable=function(){
  alert(this.someVariable);
}

Das erste was auffällt ist, dass es kein Klassen-Konstrukt wie unter Java oder CSharp gibt. Eine Klasse wird über ihren Konstruktor repräsentiert. Will man Methoden hinzufügen, so geschieht dies über den prototype. Soweit so normal und verständlich. Kommen wir nun zu den interessanten Sachen.

var myInstanz=new MyClass("SomeValue");
myInstanz.AlertVariable();
$(document).ready(myInstanz.AlertVariable);

Wer den Term „$(document)“ irritierend findet, sollte sich vorher noch mal die jQuery-Tutorials anschauen. Der normale OOP-ler würde (so er denn die Syntax versteht) erwarten, dass nun zwei mal „SomeValue“ ausgegeben wird. Führt man den JavaScript aus, kommt es nur zu einer Meldung und einem Fehlersymbol (je nach Browser). Führt man den Code im Debugger aus stellt sich schnell die Zeile „return this.someVariable;“als Fehlerquelle raus. Beim „zweiten“ Aufruf innerhalb des Events „zeigt“ der this-Operator nicht mehr auf die Instanz der Klasse sondern auf den Event-Auslöser.

Nun kann man sich einen Knoten ins Hirn machen und alles gelernte über Bord werfen und wieder „schön“ Prozedural programmieren. Damit umgeht man einen Großteil der Probleme. Es wird aber sehr schnell sehr unübersichtlich. Eine weitere Möglichkeit ist, zu tricksen. Das grundlegende Problem mit dem this-Operator lässt sich nicht lösen oder umgehen. Der man kann es nur mit anderen Strukturen „umschiffen“. Das Beispiel von Oben ein wenig umgestellt.

var myInstanz=new MyClass("SomeValue");
myInstanz.AlertVariable();
$(document).ready(function (){
  myInstanz.AlertVariable();
});

Da jetzt die Instanz der Klasse direkt angesprochen wird, funktioniert intern wieder der this-Operator wie gewohnt. Was dem verwirrten OOP-ler jetzt vollends aus der Bahn werfen dürfte, Warum zum Geier kann man aus der Subfunktion auf die Variable myInstanz zugreifen. Aber diesen Umstand verbucht man unter „nicht Fragen nur Wundern“. Leider lässt sich nicht jedes Problem so übersichtlich lösen, aber das Prinzip bleibt immer das gleiche: Der Event-Handler, der den this-Operator verbiegt wird in einem Kontext erstellt, wo die Referenz auf die Zielinstanz noch vorhanden ist (oder man stellt die Referenz her) und greift dann über die Sichtbarkeit auf diese Referenz zu. Mal ein komplexeres Problem.

function MyClass(someDomObject,displayText){
  this.alertVariable=function(){
    alert(displayText);
  };

  var alertVariable=this.alertVariable;

  $("a",someDomObject).each(function(){
    $(this).click(function(){
      alertVariable();
    });
  });
}

MyClass.prototype.AlertVariable=function(){
  this.alertVariable();
}

Ok was wird hier getrieben. Der Klasse MyClass wird ein DOM-Object übergeben. Dieses Objekt kann alles sein, auch eine XML-Node, Hauptsache es ist von jQuery verarbeitbar. Der Ausdruck $(„a“,someDomObject) selektiert jedes a-Element (<a href=““ />) und mittels der each-Funktion wird für jedes selektierte Objekt die übergebene Funktion (die inplace gebaut wird) ausgeführt. Für die ich jetzt abgehängt hab, das ganze in C-Sharp. Das würde dann so aussehen (mit LINQ).

public class MyClass
{
  private string someVariable;

  public class MyClass(XmlNode someDomObject, string displayText)
  {
    var selectedNodes = someDomObject.SelectNodes("a");

    //LINQ & Delegate
    selectedNodes.ForEach(DoSomething)

    //klassisch
    foreach(XmlNode node in selectedNodes)
    {
      DoSomething(node);
    }
  }
  
  private void DoSomething(XmlNode node)
  {
    //Die XmlNode-Klasse hat in .NET kein Click(ed)-Event ich spar mir das mal
  }
  
  public void AlertVariable()
  {
    MessageBox.Show(someVariable);
  }
}

Wirklich „brainfucked“ ist dann die „Zeile“: $(this).click(function() {}); Der this-Operator zeigt dabei nicht mehr auf die Klasse, wie man beim ersten lesen vielleicht vermuten würde, sondern „konsequenterweise“ auf das abzuarbeitende Element. In CSharp entspricht das dem Übergabewert der Funktion DoSomething. Wem sich das Konzept mal eröffnet hat, wird schnell feststellen, dass die Sichtbarkeiten wie in C/C++ gelöst wurden (einschließlich globaler Objekte). Nur dieser this-Operator ist halt ein wenig frickelig. Deswegen sieht dieser Block so „scheiße“ aus:

this.alertVariable=function(){
  alert(displayText);
};
var alertVariable=this.alertVariable;

Erst wird eine Funktion/Delegetate auf eine Funktion angelegt. Diese wird in der der Member-Variable „alertVariable“ referenziert. Auf diese kann aber innerhalb der Each->Click funktion nicht zugegriffen werden. Da der this-Operator ja umgebogen wird. Daher kopiert man die Referenz in eine lokale Variable, die wiederum ist überall verfügbar…

Wenn man diesen Spaß auf die Spitze treiben will, schreibt mal einfach sechs bis sieben Ebenen wo jeder this-Operator auf ein anderes Ziel zeigt (z.B. ein XmlDokument mit n-Ebenen die alle einzeln abgearbeitet werden müssen;)). Dass ist dann Brainfuck-delux.