Nachdem ich die Grundlagen von OOP in JavaScript geschildert habe, möchte ich mich Frameworks und deren OOP-Konzepten zuwenden. Die dort beschriebenen Sachverhalte werden hier weitesgehend als bekannt vorausgesetzt, vor allem das Wissen über Konstruktoren, Prototypen und Instanzen.
Pseudoklassen in JavaScript
Viele JavaScript-Frameworks enthalten Hilfsmittel, um komplexe Vererbungshierarchien aufzubauen. Sie nutzen die Prototypen-Ketten, um einem Objekt mehrere Prototypen zuzuweisen. Diese Helferbibliotheken erlauben die Deklaration von Pseudoklassen, um Programmierern, die klassenbasierte OOP kennen, den Einstieg zu vereinfachen. Beispiele sind Prototype (Class.create), Mootools (new Class), JS.Class (new JS.Class) und Joose (Class).
JavaScript kennt keine nativen Klassen. Ich bin mehr und mehr zu der Ansicht gekommen, dass diese Analogie das profunde Verständnis von JavaScript eher erschwert als vereinfacht. Natürlich kochen die Pseudoklassen aus Prototype, Mootools & Co. auch nur mit (klassenlosem) JavaScript. Zur gegebenen Zeit möchte ich deren interne Funktionsweise erklären, aber zunächst möchte ich mich YUI widmen.
YUI 3 – Yahoo! User Interface Library
Das Framework YUI 3 verfolgt einen etwas anderen Ansatz. Es verbirgt die tatsächliche Funktionsweise nicht hinter Abstraktionen wie Pseudoklassen und fühlt sich daher mehr wie JavaScript an. YUI bringt nichtsdestoweniger brauchbare Hilfsmittel für Mehrfachvererbung mit sich. Anstelle von Pseudoklassen-Deklarationen bietet YUI verschiedene Helferfunktionen. Diese suggerieren nicht, das »Klassen« erzeugt werden, sondern versuchen der funktionalen, prototypischen und dynamischen Natur von JavaScript Rechnung zu tragen.
YUI arbeitet stark gekapselt, asynchron und modularisiert. Die hier vorgestellten Techniken sind entweder Teil des Kerns oder des oop
-Moduls. Der hier gezeigte Code muss zumindest in folgenden Aufruf gekapselt werden:
<script src="http://yui.yahooapis.com/3.1.1/build/yui/yui-min.js"></script> <script> YUI().use('oop', function (Y) { /* Der Code kommt hierhin. */ }); </script>
Zunächst wird der sogenannte YUI Seed eingebunden, mithilfe dessen weitere YUI-Module geladen werden können. Über YUI().use('oop')
geben wir Code an, der auf dem oop
-Modul aufbaut. YUI lädt automatisch die benötigten JavaScript-Dateien, erzeugt ein Objekt mit den gewünschten Modulen und übergibt es an die Callback-Funktion. Darin ist das Objekt unter dem Namen Y
verfügbar.
Das oop
-Modul wird von fast allen gängigen YUI-Modulen, darunter dom
verwendet. Wenn Sie YUI für die üblichen Zwecke des DOM-Scripting verwenden, dann wird oop
in der Regel als abhängiges Modul bereits eingebunden.
Y.Object
– Einfache prototypische Vererbung ohne (sichtbare) Konstruktoren-
Y.extend
– Konstruktoren und mehrfache prototypische Vererbung - Mixins mit
Y.augment
- Eigenschaften kopieren mit
Y.mix
- Links und Quellen
Y.Object
– Einfache prototypische Vererbung ohne (sichtbare) Konstruktoren
Da JavaScript-OOP nicht auf Klassen, sondern Prototypen basiert, ist die Grundidee, dass gewöhnliche Objekte von gewöhnlichen Objekten abgeleitet werden. Leider kann die Prototypen-Ketten nicht browserübergreifend direkt manipuliert werden, sondern es ist der Umweg über Konstruktoren, deren prototype
-Eigenschaft sowie die Instantiierung mit new
nötig. Dies ist aus klassenbasierten Sprachen wie Java bekannt – in JavaScript aber oftmals schwerfällig und fehl am Platze.
Die Helferfunktion Y.Object
ermöglicht direkte prototypische Vererbung. Sie erzeugt ein neues Objekt, welches ein anderes als Prototyp besitzt. Y.Object
nimmt uns den Umweg über eine Konstruktorfunktion ab. Intern wird durchaus mit einem Konstruktor, seiner prototype
-Eigenschaft und new
gearbeitet, allerdings wird mit der Funktion nur eine Instanz erzeugt und sie wird daraufhin weggeworfen. So sieht der Quellcode von Y.Object
aus:
Y.Object = function(o) { var F = function() {}; F.prototype = o; return new F(); };
(An dem Funktionsobjekt Y.Object
hängen übrigens noch weitere Funktionen – wenn hier von Y.Object
die Rede ist, dann immer von der obigen Funktion, nicht deren Kindmethoden.)
Dieses Pattern hat Douglas Crockford »erfunden«, der nicht zufällig als JavaScript-Architekt bei Yahoo angestellt ist. Diese Funktion ist als Object.create bekannt und gehört seit ECMAScript 5 zum Standardrepertoire von JavaScript.
Die Anwendung gestaltet sich sehr einfach. Man erzeugt einfache Objekte mit dem Objekt-Literal und leitet mit Y.Object
neue Objekte von diesen ab. Diese wiederum können modifiziert und erweitert werden:
var bird = { flighted: true, noise: "Chirp, chirp!", sing: function () { alert(this.noise); } }; var robin = Y.Object(bird); robin.sing(); var chicken = Y.Object(bird); chicken.flighted = false; chicken.noise = "Cluck, cluck!"; var ginger = Y.Object(chicken); ginger.name = "Ginger"; var rocky = Y.Object(chicken); rocky.name = "Rocky";
Das Beispiel erzeugt ein bird
-Objekt, einen Vogel. Dieser wird als flugfähig angenommen (flighted
) und kann Laute von sich geben (Methode sing
).
Davon abgeleitet werden das robin
- sowie das chicken
-Objekt. Das chicken
-Objekt bekommt etwas abweichende Eigenschaften, denn das Huhn kann nicht fliegen und gackert, anstatt zu zwitschern. Schließlich werden zwei weitere Objekte angelegt, die von chicken
erben.
Immer wenn ein Objekt mit der Funktionalität von bird
benötigt wird, wird mittels Y.Object
ein neues Objekt erzeugt, welches bird
als Prototypen besitzt. Wird davon ein weiteres Objekt abgeleitet, besitzt es zwei Prototypen (ginger
hat die Prototypen chicken
und bird
).
Für Programmierer, die gedanklich in klassenbasierter OOP verhaftet sind, mag das ein Albtraum sein: Es gibt keine Trennung zwischen abstrakten Typdeklarationen einerseits und Instanzen andererseits, sondern bloß Objekte und Prototypen. Wenn man jedoch einmal prototypische Vererbung verstanden hat, so erscheint mir obiges Pattern einfacher und logischer, als in jedem Fall ausdrücklich mit Konstruktorfunktionen, prototype
und new
zu arbeiten. Object.create
bzw. Y.Object
ermöglichen die Abstraktion von dieser internen Umsetzung.
Y.extend
– Konstruktoren und mehrfache prototypische Vererbung
Neben dem radikalen prototypischen Ansatz von Y.Object
, der Konstruktoren und Instantiierung wegkapselt, erlaubt YUI auch pseudoklassische Vererbung. Die YUI-Helferfunktion Y.extend
geht davon aus, dass mit üblichen Konstruktoren gearbeitet wird, mit denen mittels new
Objekte (»Instanzen«) erzeugt werden.
Das Beispiel aus der YUI-Dokumentation erzeugt einen Konstruktor namens Bird
, mit dem Vögel erzeugt werden können. Die Eigenschaften des zugehörigen Prototypen kennen wir bereits.
function Bird (name) { this.name = name; } Bird.prototype = { flighted: true, noise: "Chirp, chirp!", sing: function () { alert(this.noise); } }; var robin = new Bird("Robin"); robin.sing(); alert("Is " + robin.name + " flighted? " + (robin.flighted ? "yes" : "no"));
Von diesem allgemeinen Objekttyp wird nun ein speziellerer Typ Chicken
mit abweichenden Eigenschaften abgeleitet:
function Chicken (name) { Chicken.superclass.constructor.call(this, name); // dasselbe wie Bird.call(this, name); } Y.extend(Chicken, Bird); Chicken.prototype.flighted = false; Chicken.prototype.noise = "Cluck, cluck!"; Chicken.prototype.sing = function () { alert("The chicken " + this.name + " says:"); Chicken.superclass.sing.call(this); // dasselbe wie Bird.prototype.sing.call(this); }; var ginger = new Chicken("Ginger"); ginger.sing(); alert("Is " + ginger.name + " flighted? " + (ginger.flighted ? "yes" : "no"));
Der Aufruf von Y.extend
stellt die Vererbungshierarchie her. Zwischen Chicken
und Bird
besteht eine »Ist ein«-Beziehung. Der erste Parameter ist der Konstruktor, dessen Instanzen Fähigkeiten erben, der zweite Parameter der Konstruktor, dessen Prototyp Fähigkeiten bereitstellt.
Der Chicken
-Prototyp überschreibt drei Eigenschaften, darunter eine Methode. Im Gegensatz zu Pseudoklassen in Mootools und Prototype stellt Y.extend
keinen direkten Super-Verweis bereit. Das heißt, die gleichnamige Methode des allgemeineren Prototypen ist nicht über eine Instanzeigenschaft (wie bei Mootools) oder einen Methodenparameter (wie bei Prototype) zugänglich. Stattdessen erzeugt Y.extend
eine Eigenschaft namens superclass
beim Chicken
-Konstruktor. Diese Eigenschaft verweist auf den Prototypen von Bird
. Um diese Funktionen im Kontext der Instanz auszuführen, kann call
oder apply
verwendet werden.
Dieser superclass
-Verweis soll eine Vereinfachung darstellen, alternativ kann man direkt auf Bird
bzw. Bird.prototype
zugreifen, wie das obige Beispiel zeigt. Y.extend
verzichtet hier absichtlich auf Komfort und »Syntactic Sugar«, sondern setzt auf nur unwesentlich vereinfachtes Low-Level-JavaScript. Der Vorteil ist, dass sich YUI damit einige Probleme vom Hals hält. Mootools und Prototype müssen einige Verrenkungen machen, um Super-Aufrufe wie in klassenbasierter OOP zu ermöglichen. Super-Aufrufe kommen nicht allzu häufig vor, daher halte ich YUIs Entscheidung für in Ordnung.
Interne Funktionsweise von Y.extend
Spannend ist nun, was Y.extend
intern macht. Es sind im Grunde vier Schritte:
-
var chickenPrototype = Y.Object(Bird.prototype);
Mittels dem beschriebenen
Y.Object
wird ein neues, leeres Objekt angelegt, dasBird.prototype
als Prototypen hat. -
Chicken.prototype = chickenPrototype;
Dieses neue Objekt, das durch prototypische Vererbung über die Fähigkeiten von
Bird.prototype
verfügt, wird der neueChicken
-Prototyp. Danach kann manChicken.prototype
erweitern und Eigenschaften »überschreiben«, ohne dassBird.prototype
angetastet wird. -
Chicken.prototype.constructor = Chicken;
Die
constructor
-Eigenschaft fürChicken
-Instanzen wird auf die richtige Funktion, nämlichChicken
gesetzt. -
Chicken.superclass = Bird.prototype;
Die Konstruktorfunktion
Chicken
bekommt eine Eigenschaftsuperclass
, welche aufBird
verweist.
Diese Schritte erzeugen eine Vererbungshierarchie über die Prototypen-Kette. Die Kette sieht letztlich so aus:
ginger
[[Prototype]] | Chicken.prototype |
---|---|
name | "Ginger" |
constructor | Chicken |
Chicken.prototype
[[Prototype]] | Bird.prototype |
---|---|
flighted | false |
noise | "Cluck, cluck!" |
sing | function () { … } |
constructor | Object |
Bird.prototype
[[Prototype]] | Object.prototype |
---|---|
flighted | true |
noise | "Chirp, chirp!" |
sing | function () { … } |
constructor | Object |
Object.prototype
[[Prototype]] | null |
---|---|
constructor | Object |
toString | [native Funktion] |
toLocaleString | [native Funktion] |
valueOf | [native Funktion] |
hasOwnProperty | [native Funktion] |
isPrototypeOf | [native Funktion] |
propertyIsEnumerable | [native Funktion] |
Die Chicken
-Instanz ginger
hat also drei Prototypen, nämlich 1. Chicken.prototype
, 2. Bird.prototype
und schließlich der Prototyp aller Objekte, Object.prototype
.
Aus diesem Grund funktioniert auch die Anwendung des instanceof
-Operators:
alert(ginger instanceof Chicken); // true alert(ginger instanceof Bird); // true
instanceof
schaut, ob der Prototyp der angegebenen Funktion (also Chicken.prototype
bzw. Bird.prototype
) in der Prototype-Kette des angegebenen Objekts (ginger
) existiert.
Die Vererbungshierarchie, die wir direkt mit Y.Object
angelegt haben, sieht übrigens genauso aus. Es fehlt dort lediglich die Trennung zwischen Instanzen, Konstruktoren und Prototypen, stattdessen gibt es nur Objekte, die andere Objekte als Prototyp besitzen.
Weitere Parameter von Y.extend
: Kompaktschreibweise und statische Eigenschaften
Y.extend
nimmt zwei weitere optionale Parameter entgegen. Beide sind Objekte.
Mit dem dritten Parameter lassen sich Eigenschaften angeben, die an den abgeleiteten Prototyp gehängt werden. Dies erlaubt folgende kompakte Schreibweise:
var Chicken = Y.extend( // Konstruktor function () { Chicken.superclass.constructor.call(this, name); }, // Super-Konstruktor Bird, // Chicken-Prototyp { flighted: false, noise: "Cluck, cluck!", sing: function () { alert("The chicken " + this.name + " says:"); Chicken.superclass.sing.call(this, name); // dasselbe wie Bird.prototype.sing.call(this, name); } } ); var ginger = new Chicken("Ginger"); ginger.sing(); alert("Is " + ginger.name + " flighted? " + (ginger.flighted ? "yes" : "no"));
Das obige Beispiel macht genau dasselbe wie das erste Beispiel. Y.extend
gibt immer den ersten Parameter zurück, also die Konstruktorfunktion. Wir können sie als anonyme Funktion übergeben und den Rückgabewert von Y.extend
in einer Variable speichern.
Mit dem vierten Parameter von Y.extend
lassen sich Eigenschaften angeben, die an die Konstruktorfunktion selbst gehängt werden (statische Eigenschaften, wie sie in der klassenbasierten OOP genannt werden). Das folgende Beispiel nutzen dies, um Eigenschaften direkt an Chicken
zu hängen. Wir nutzen dies für einen Zähler, der die Nummer der erzeugten Chicken
-Instanzen registriert:
var Chicken = Y.extend( // Konstruktor function () { Chicken.superclass.constructor.call(this, name); Chicken.number++; }, // Super-Konstruktor Bird, // Chicken-Prototyp { flighted: false, noise: "Cluck, cluck!", sing: function () { alert("The chicken " + this.name + " says:"); Chicken.superclass.sing.call(this, name); // dasselbe wie Bird.prototype.sing.call(this, name); } }, // Eigenschaften von Chicken { number: 0 } ); var ginger = new Chicken("Ginger"); var rocky = new Chicken("Rocky"); alert(Chicken.number + " chicken created");
Y.extend
ist sehr einfach aufgebaut, bietet aber alles, was für eine prototypische Vererbungshierarchie nötig ist. Die Helferfunktion wird nahezu überall im YUI-Framework mitsamt seinen dutzenden Modulen verwendet.
Mixins mit Y.augment
Mit Y.augment
lassen sich Mixins zwischen Pseudoklassen (Konstruktoren-Prototypen-Paaren) umsetzen. Y.augment
kopiert die Eigenschaften, anstatt eine Vererbung mittels Prototypen-Kette aufzusetzen.
Y.augment
ist sinnvoll, wenn sich die Prototypen zweier Konstruktoren Funktionalität teilen sollen, ohne dass eine strenge Hierarchie zwischen ihnen besteht wird. Die einfachste Aufrufweise lautet:
Y.augment(Zielkonstruktor, Quellkonstruktor);
In diesem Fall werden die Eigenschaften von Quellkonstruktor.prototype
nach Zielkonstruktor.prototype
kopiert.
Intern wird dazu Y.mix
verwendet, welches weiter unten noch genauer erklärt wird. Es gibt allerdings einen Unterschied: Es wird nicht einfach Y.mix(Zielkonstruktor.prototype, Quellkonstruktor.prototype);
aufgerufen. Beim Kopieren der Methoden (Funktionsobjekte) werden die Originalfunktionen jeweils durch eine Wrapperfunktion ersetzt. Wenn diese Wrapperfunktion aufgerufen wird, dann wird die Originalfunktion im Kontext der Instanz ausgeführt. Nach dem ersten Aufruf einer geerbten Methode werden alle Wrapperfunktionen durch die Originalfunktionen ersetzt.
Der Sinn davon ist, dass der Quellkonstruktor automatisch aufgerufen wird, sobald eine geerbte Methode aufgerufen wird. Bei der hierarchischen Vererbung mit Y.extend
haben wir den Super-Konstruktor mittels Chicken.superclass.constructor.call(this, name);
manuell aufgerufen. Y.augment
nimmt uns dies ab. Sobald eine geerbte Methode aufgerufen wird, wird der zugehörige Konstruktor einmal aufgerufen. So ist garantiert, dass der Konstruktor das Objekt initialisiert hat, damit die Methoden auch wie gewünscht funktionieren.
Ein angepasstes Beispiel aus der YUI-Dokumentation. Wir haben einen einfachen Konstruktor samt Prototyp:
function Bird () { alert("Bird constructor called"); } Bird.prototype.sing = function () { alert("Chirp, chirp!"); };
Dem Bird
-Typ wollen wir nun eine abstrakte Funktionalität Flighted
(flugfähig) hinzufügen. Dazu definieren wir ein Mixin ebenfalls mit Konstruktor und Prototyp:
function Flighted () { alert("Flighted constructor called"); // Beispiel für eine Initialisierung: this.flying = false; } Flighted.prototype = { fly: function () { alert("I'm flying high!"); this.flying = true; }, land: function () { this.flying = false; } };
Flighted
ist nicht dazu gedacht, direkt instantiiert zu werden, stattdessen stellt dieses Konstruktor-Prototype-Paar wiederverwendbare Funktionalität bereit, die in andere Typen »eingemixt« werden kann. Dazu nutzen wir nun Y.augment
:
Y.augment(Bird, Flighted); var bird = new Bird(); bird.sing(); bird.fly();
Beim ersten Aufruf von fly
ist dies noch eine Wrapperfunktion, welche den Flighted
-Konstruktor sowie die Originalfunktion aufruft. Darin kann etwa eine nötige Initialisierung erfolgen. Vergleichen wir die fly
-Methode vor und nach ihrem Aufruf mit der Originalfunktion beim Flighted
-Prototypen, so kommen wir der Ersetzung auf die Spur:
Y.augment(Bird, Flighted); var bird = new Bird(); bird.sing(); alert(bird.fly === Flighted.prototype.fly); // … ergibt false, verweist auf eine Wrapperfunktion bird.fly(); // Ruft indirekt den Flighted-Konstruktor auf alert(bird.fly === Flighted.prototype.fly); // … ergibt true, verweist nun auf die Originalfunktion
Einzelnen Objekten Funktionalität hinzufügen
Y.augment
muss nicht mit zwei Konstruktorfunktionen als Parameter aufgerufen werden. Es ist genauso möglich, die Eigenschaften eines Prototyps in ein einfaches Objekt einzumixen, wenn nicht alle Instanzen, sondern nur um eine bestimmte Funktionalität erweitert werden soll:
var hedwig = new Bird(); Y.augment(hedwig, Flighted); hedwig.fly();
Weitere Y.augment
-Parameter: Überschreiben erzwingen und Allow-List
Y.augment
nimmt noch drei weitere, optionale Parameter entgegen. Der dritte Parameter vom Typ Boolean gibt an, ob bereits vorhandene Methoden beim Kopieren überschrieben werden sollen. Standard ist false
. Mit dem vierten Parameter lässt sich eine Liste erlaubter Eigenschaften (Allow-List) übergeben, das ist ein Array mit String-Einträgen. Der fünfte Parameter erlaubt die Angabe eines Arrays mit Parametern, die dem vererbenden Konstruktor (im Beispiel Flighted
) übergeben werden.
Eigenschaften kopieren mit Y.mix
Y.mix
wurde bereits angesprochen: Es ist eine allgemeinere Funktion zum Kopieren von Eigenschaften zwischen zwei Objekten, die intern von Y.extend
und vor allem Y.augment
verwendet wird. Y.mix
ermöglicht nicht nur das Erweitern von Prototypen bzw. das Erweitern der Prototypen-Kette, sondern besitzt verschiedene Kopiermodi. Die YUI-Dokumentation enthält ein Beispiel zum Kombinieren zweier Module, die mit dem Revealing Module Pattern notiert wurden. Das halte ich für einen verbreiteten Anwendungsfall.
var Modul = (function () { var modulname = "Modul"; return { methode1: function () { alert(modulname + '.methode1 wurde aufgerufen'); } }; })(); var Erweiterung = (function () { var modulname = "Erweiterung"; return { methode2: function () { console.log(modulname + '.methode2 wurde aufgerufen'); } }; })(); Y.mix(Modul, Erweiterungsmodul); Modul.methode1(); Modul.methode2();
Bekannt sein sollte hier, dass die einzelnen gekapselten Module nicht gegenseitig auf ihre privaten Objekte zugreifen können.
Auch bei vereinfachter prototypischer Vererbung mit Y.Object
bietet sich Y.mix
an, um die Funktionalität eines anderen Objekts einzumixen:
var bird = { noise: "Chirp, chirp!", sing: function () { alert(this.noise); } }; var flighted = { flying: false, fly: function () { alert("I'm flying high!"); this.flying = true; }, land: function () { this.flying = false; } }; var robin = Y.Object(bird); Y.mix(robin, flighted); robin.sing(); robin.fly();
Y.mix
erzeugt standardmäßig eine sogenannte flache Kopie (shallow copy). Das heißt, dass Objekte wie etwa Arrays bloß referenziert werden und keine echte Kopie von ihnen angelegt wird. Für eine tiefe Kopie (deep copy) ist das hier nicht besprochene Y.clone zuständig, für das kontrollierte Erweitern von Eigenschaften vom Typ Objekten und Arrays gibt es die Methode Y.aggregate.
Weitere Parameter von Y.mix
: Überschreiben erzwingen und Allow-List
Y.mix
nimmt insgesamt sechs Parameter entgegen, davon sind die letzten vier optional. Interessant sind noch der dritte und vierte Parameter, wir haben sie schon bei Y.augment
kennengelernt: Der dritte gibt an, ob gleichnamige Eigenschaften beim Kopieren überschrieben werden. Dies ist standardmäßig nicht der Fall und kann mit true
erzwungen werden. Der vierte Parameter ermöglicht die Übergabe einer weißen Liste (Allow-List). Diese wird als Filter verwendet, sodass nur die angegebenen Eigenschaften kopiert werden.
Mehrere Objekte vereinen mit Y.merge
Y.merge
ist ein kleiner, spezieller Wrapper für Y.mix
. Der einzige Unterschied ist, dass beliebig viele Objekte übergeben werden können. Der zweite und alle folgenden Parameter geben Objekte an, deren Eigenschaft in das Objekt kopiert werden, das als erster Parameter übergeben wird:
Y.merge(Modul, Erweiterung1, Erweiterung2, Erweiterung3);
Y.augment
vs. Y.mix
Der entscheidende Unterschied zwischen Y.mix
und Y.augment
, die »Lazy Augmentation« durch den verzögerten Konstruktoraufruf, ist leider nicht ausreichend dokumentiert. Dies mag innerhalb des YUI-Frameworks häufig vorkommen und scheint mir auch ganz klug: Es verzögert die Initialisierung des Mixins bis zu dem Punkt, an dem die vererbten Methoden tatsächlich genutzt werden. Wenn ein solches Setup mittels Konstruktorfunktion nicht nötig ist, gibt es keinen Vorteil von Y.augment
gegenüber dem einfachen Y.mix
.
Links und Quellen
Für die vorgestellten Funktionen gibt es relative gute Beispiele in der YUI-Dokumentation, die ich hier adaptiert habe. Darüber hinaus zwei exzellente Blogartikel von Stoyan Stefanov, die ein größeres Bild zeichnen und die Hintergründe noch detaillierter erklären, als ich es hier versucht habe.
- YUI 3 Examples: Create Class Hierarchies with
extend
- YUI 3 Examples: Compose Classes of Objects with
augment
- YUI 3 Examples: Add Behaviors to Objects with
mix
- API-Dokumentation für
Y.augment
- API-Dokumentation für
Y.extend
- API-Dokumentation für
Y.mix
- YUI Blog: Inheritance Patterns in YUI 3
- YUI Blog: More code reuse patterns in YUI3
- YUI Theater: YUI 3 Sugar, Todd Kloots