If you're seeing this message, it means we're having trouble loading external resources on our website.

Wenn du hinter einem Webfilter bist, stelle sicher, dass die Domänen *. kastatic.org und *. kasandbox.org nicht blockiert sind.

Hauptinhalt

Ein Button-Objekttyp

Eine der besten Methoden, um Code wiederverwendbar zu machen, ist es, objektorientierte Programmierung zu verwenden, besonders für die Erstellung von Benutzeroberflächen (UI) wie Buttons. Bei der objektorientierten Programmierung stellen wir uns die Programmwelt so vor, dass Objekte ein bestimmtes Verhalten haben, und erstellen dann spezielle Instanzen dieser Objekte mit bestimmten Parametern. Falls du dich nicht mehr erinnern kannst, wie man das in JavaScript macht, kannst du es hier wieder auffrischen.
Um mithilfe von OOP Buttons zu erstellen, müssen wir erst einen Objekttyp Button definieren und anschließend die dazugehörigen Methoden wie das Zeichnen und das Ausführen von Mausklicks. Wir würden gern Code schreiben können, der folgendermaßen aussieht:
var btn1 = new Button(...);
btn1.draw();

mouseClicked = function() {
  if (btn1.isMouseInside()) {
     println("Whoah, you clicked me!");
  }
}
Vergleichen wir das mal mit dem Code, den wir im letzten Artikel geschrieben haben:
var btn1 = {...};
drawButton(btn1);

mouseClicked = function() {
  if (isMouseInside(btn1)) {
     println("Whoah, you clicked me!");
  }
}
Sie sind sich sehr ähnlich, oder? Allerdings gibt es einen großen Unterschied -- alle Funktionen sind über den Objekttyp Button definiert und gehören damit direkt zu den Buttons. Eigenschaften und Verhalten sind damit enger verknüpft, womit der Code sauberer und leichter wiederzuverwenden ist.
Um den Objekttyp Button zu definieren, müssen wir mit dem Konstruktor beginnen: der speziellen Funktion, mit der Konfigurationsparameter aufgenommen und auf Objektebene definiert werden.
Als ersten Versuch nehmen wir mal einen Konstruktur, der x, y, Breite ("width") und Höhe ("height") aufnimmt.
var Button = function(x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
};

var btn1 = new Button(100, 100, 150, 150);
Das wird sicherlich funktionieren, ich empfehle aber eine andere Herangehensweise. Anstatt einzelner Parameter könnte der Konstruktor ein Konfigurationsobjekt aufnehmen.
var Button = function(config) {
    this.x = config.x;
    this.y = config.y;
    this.width = config.width;
    this.height = config.height;
    this.label = config.label;
};
Das Konfigurationsobjekt hat den Vorteil, dass wir dem Konstruktur weitere Parameter hinzufügen können (wie label) und wir trotzdem weiterhin leicht verstehen können, wofür jeder Parameter steht, während wir den Button bauen:
var btn1 = new Button({
    x: 100, y: 100,
    width: 150, height: 50,
    label: "Please click!"});
Aber wir können sogar noch einen Schritt weiter gehen. Was, wenn die meisten Buttons die gleiche Breite oder Höhe haben? Dann sollten wir die Breite und Höhe nur für die Buttons angeben müssen, bei denen es notwendig ist. Wir können den Konstruktur prüfen lassen, ob die Eigenschaft in dem Konfigurationsobjekt tatsächlich definiert ist, und, falls nicht, auf einen definierten Standardwert zurückgreifen. Zum Beispiel so:
var Button = function(config) {
    this.x = config.x || 0;
    this.y = config.y || 0;
    this.width = config.width || 150;
    this.height = config.height || 50;
    this.label = config.label || "Click";
};
Jetzt können wir ihn nur mit einem Teil der Eigenschaften aufrufen, weil die anderen durch Standardwerte definiert sind:
var btn1 = new Button({x: 100, y: 100, label: "Bitte klicken!"});
All die Arbeit nur für einen Konstruktur? Aber glaubt mir, das ist es wert.
Nun da wir einen kunstvollen Konstruktur geschaffen haben, können wir dem Button sagen, was er zu tun hat: zunächst mit der Methode draw. Der Code ist der gleiche wie für die Funktion drawButton, holt sich jedoch alle Eigenschaften aus this, weil er selbst im Objektprototyp definiert ist:
Button.prototype.draw = function() {
    fill(0, 234, 255);
    rect(this.x, this.y, this.width, this.height, 5);
    fill(0, 0, 0);
    textSize(19);
    textAlign(LEFT, TOP);
    text(this.label, this.x+10, this.y+this.height/4);
};
Nun da dies alles definiert ist, können wir es so aufrufen:
btn1.draw();
Das folgende Programm verwendet dieses Objekt Button, um zwei Buttons anzulegen – schau mal, wie einfach es ist, mehrere Buttons anzulegen und zu zeichnen.
Den schwersten Teil haben wir allerdings übersprungen: das Ausführen von Klicks. Am Anfang können wir eine Funktion für den Prototyp Button definieren, die "true" ausgibt, wenn der Nutzer innerhalb der Begrenzung eines Buttons klickt. Auch hier ist die Funktion die gleiche wie vorher, außer dass sie alle ihre Eigenschaften aus this holt anstatt aus einem enthaltenen Objekt.
Button.prototype.isMouseInside = function() {
    return mouseX > this.x &&
           mouseX < (this.x + this.width) &&
           mouseY > this.y &&
           mouseY < (this.y + this.height);
};
Jetzt können wir diese Form innerhalb einer Funktion mouseClicked verwenden:
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
Probier es unten aus, indem du auf jeden Button klickst.
Aber etwas passt mir nicht bei der Art und Weise, wie wir das Ausführen von Klicks definiert haben. Der ganze Sinn objektorientierter Programmierung liegt doch darin, das gesamte Verhalten eines Objekts im Objekt selbst zu bündeln und anschließend das Verhalten mit individuellen Eigenschaften anzupassen. Ein paar Verhaltensweisen haben wir jedoch außen vor gelassen, nämlich die println innerhalb von mouseClicked:
mouseClicked = function() {
    if (btn1.isMouseInside()) {
        println("You made the right choice!");
    } else if (btn2.isMouseInside()) {
        println("Yay, you picked me!");
    }
};
Diese Print-Anweisungen sollten besser direkt an die einzelnen Buttons geknüpft werden, wie etwas, das wir in den Konstruktur eingeben. Wenn wir uns den aktuellen Zustand ansehen, könnten wir zu dem Entschluss kommen, eine Meldung ("message") in die Konstrukturkonfiguration einzugeben und zum Drucken ("println") eine Funktion handleMouseClick zu definieren.
var Button = function(config) {
    ...
    this.message = config.message || "Clicked!";
};

Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
         println(this.message);
    }
};

var btn1 = new Button({
    x: 100,
    y: 100,
    label: "Please click!",
    message: "You made the right choice!"
});

mouseClicked = function() {
   btn1.handleMouseClick();
};
Das ist viel schöner, weil damit alles, was mit dem speziellen Verhalten eines Buttons zu tun hat, sauber in den Konstruktor gepackt wurde. Aber es ist leider auch zu einfach. Was machen wir, wenn wir etwas anderes tun wollen als eine Meldung drucken, wie ein paar Formen zeichnen oder Szenen wechseln, also etwas, für das wir ein paar Zeilen an Code brauchen? In diesem Fall müssen wir dem Konstruktor mehr als einen String zur Verfügung stellen – nämlich ein Stück Code. Wie können wir ein Stück Code weitergeben?
...mit einer Funktion! In JavaScript (jedoch nicht in allen Sprachen) können wir Funktionen als Parameter in Funktionen eingeben. Das ist in vielen Situationen nützlich, besonders nützlich ist es jedoch bei der Definition von GUI-Elementen wie Buttons. Wir können dem Button sagen: Hey, hier ist eine Funktion, ein Stück Code, das du aufrufen sollst, wenn der Nutzer den Button anklickt. Wir nennen diese Funktionen "Rückruf"-Funktionen, weil sie nicht sofort, sondern später zu einer passenden Zeit "zurückgerufen" werden.
Wir können damit beginnen, dass wir einen Parameter onClick weitergeben, bei dem es sich um eine Funktion handelt.
var btn1 = new Button({
    x: 100,
    y: 100,
    label: "Please click!",
    onClick: function() {
       text("You made the right choice!", 100, 300);
    }
});
Dann müssen wir sicherstellen, dass unser Konstruktor die Eigenschaft onClick richtig definiert, je nachdem was eingegeben wird. Als Standardeingabe, für den Fall, dass kein onClick eingegeben wird, erstellen wir eine "no-op"-Funktion -- eine Nulloperation, die keine Funktionen ("no operations") ausführt. Sie ist nur dafür da, dass wir sie ausgeben können, um keinen Fehler zu erhalten:
var Button = function(config) {
    // ...
    this.onClick = config.onClick || function() {};
};
Am Ende müssen wir noch die Rückruffunktion aufrufen, sobald der Nutzer den Button anklickt. Das ist eigentlich ganz einfach – wir können sie einfach aufrufen, indem wir den Namen der Eigenschaft schreiben, unter der wir sie gespeichert haben, gefolgt von leeren Klammern:
Button.prototype.handleMouseClick = function() {
    if (this.isMouseInside()) {
        this.onClick();
    }
};
Und jetzt sind wir fertig – wir haben ein Objekt Button, aus dem wir einfach neue Buttons machen können, die unterschiedlich aussehen und unterschiedlich auf Klicks reagieren. Klicke in dem untenstehenden Beispiel herum, und schau was passiert, wenn du Button-Parameter änderst:
Jetzt wo du eine Vorlage hast, könntest du Buttons auch auf andere Weise anpassen, zum Beispiel mit unterschiedlichen Farben, oder sie auf andere Ereignisse reagieren lassen, zum Beispiel Mouseover. Probiere es in deinen Programmen aus!

Willst du an der Diskussion teilnehmen?

Noch keine Beiträge.
Verstehst du Englisch? Klick hier, um weitere Diskussionen auf der englischen Khan Academy Seite zu sehen.