Datenbank erstellen – Programmieren lernen mit JavaScript – Thytos
Nächstes Video startet in 3 Sekunden.
Programmieren lernen mit JavaScript

Datenbank erstellen

Da­ten­ban­ken sind Mo­du­le, um Da­ten zu ver­wal­ten und lang­fris­tig zu spei­chern. Um in Ja­va­Script ein Mo­dul zur Da­ten­ver­wal­tung zu er­stel­len, kann da­für eine Kon­struk­tor­funk­tion ge­schrie­ben werden.

Eins direkt vorweg: In diesem Artikel wird keine "echte" Datenbank erstellt, die in der wirklichen Welt eingesetzt werden könnte. Vielmehr wird anhand eines Modul das Prinzip von Datenverwaltung erklärt und gezeigt, wie eigene Objekte in JavaScript konstruiert werden.

Eigene Objekte erstellen

Wenn ein Array oder ein Datumsobjekt oder ein einfaches Objekt erstellt wird, passiert das mit dem new Operator und einer Konstruktorfunktion.

new Array(); // Abgekürzt auch per [] möglich

new Date();

new Object(); // Abgekürzt auch per {} möglich

Das Datenbankmodul, das in diesem Artikel erstellt wird, soll ebenfalls auf diese Weise konstruiert werden. Doch wie funktioniert das?

Soll das Modul per new Database() erstellt werden, muss eine Konstruktorfunktion namens Database angelegt werden.

function Database() {}

Anschließend kann eine Instanz dieses Konstruktors mit dem new-Operator generiert werden.

new Database();
// => Database {}

CRUD: Die grundlegenden Datenbankoperationen

Zur Verwaltung von Daten werden vier grundlegende Operationen benötigt: Create, Read, Update und Delete, abgekürzt CRUD.

  • Create, Erstellen: Einen Datensatz hinzufügen
  • Read, Auslesen: Einen Datensatz auslesen
  • Update, Aktualisieren: Einen Datensatz verändern
  • Delete, Löschen: Einen Datensatz entfernen

Diese vier Operationen sollen in der Datenbank mit folgenden Funktionen implementiert werden:

  • insert: Nimmt ein Objekt an und fügt es einem Datensatz hinzu
  • where: Nimmt einen Propertynamen und einen Wert an und durchsucht den Datensatz nach Objekten, in denen die übergebene Property dem übergebenen Wert entspricht. Werden keine Argumente übergeben, gibt es den gesamten Datensatz zurück.
  • update: Nimmt einen Propertynamen und einen Wert an, um anhand dessen das zu verändernde Objekt zu finden, und nimmt darüberhinaus einen weiteren Propertynamen und Wert an, mit dem es das gefundene Objekt verändert.
  • delete: Nimmt einen Propertynamen und einen Wert an, um anhand dessen das zu löschende Objekt zu finden, und entfernt es aus dem Datensatz. Werden keine Argumente übergeben, wird der gesamte Datensatz gelöscht.

Der Datensatz in dieser Datenbank ist einfach nur ein Array, das befüllt wird.

Das this keyword

Doch wie werden die Funktionen dem Datenbank-Modul hinzugefügt?
Sie könnten an die Instanz gebunden werden, doch das ist keine gute Idee.

var meinDatensatz = [];
var meineDatenbank = new Database();
meineDatenbank.insert = function (obj) {
  meinDatensatz.push(obj);
};
meineDatenbank.where = function (prop, val) {
  // Richtige Implementierung folgt später
  // Hier wird erstmal nur der Datensatz zurückgegeben:
  return meinDatensatz;
}

meineDatenbank.insert({ ein: "Objekt" });
meineDatenbank.where();
// => [{ ein: "Objekt" }]

Grundsätzlich funktioniert das zwar, doch diese Umsetzung ist wenig sinnvoll:

  • Der Datensatz ist als Variable außerhalb des Datenbankmoduls definiert. Das bedeutet zum einen, dass jeder beliebig darauf zugreifen kann und somit das Datenbankmodul weniger ein Modul ist als eine Ansammlung von Helfer-Funktionen, um mit der Variable zu arbeiten.
  • Die Funktionen insert und where greifen direkt auf die Variable meinDatensatz zu, die außerhalb und unabhängig von ihnen im Vorfeld definiert wurde. Falls die Variable nicht existiert, würden die Funktionen Fehler werfen. Ein Modul sollte in sich geschlossen funktionieren und nicht darauf hoffen, dass außerhalb irgendwelche Variablen definiert wurden.
  • Die Datenbank-Funktionen sind direkt an der Instanz meineDatenbank definiert. Wird eine weitere Instanz erstellt, besitzt diese weder die insert- noch die where-Methode – sie müssten erneut an der neuen Instanz definiert werden. Auf diese Weise ist das Datenbank-Modul sinnlos.

Die Methoden sollten direkt nach der Instanziierung zur Verfügung stehen und jede Instanz ihren eigenen Datensatz beinhalten.
Das ist möglich mit dem this-Schlüsselwort: this referenziert innerhalb von Objektmethoden die jeweilige Instanz, zu dem die Methode gehört.
Dadurch können das Datensatz-Array und die insert- und where-Methode in der Konstruktorfunktion definiert werden, um daraufhin bei jeder Instanz direkt zur Verfügung zu stehen.

function Database() {
  this.datensatz = []; // Hier werden die Daten gespeichert
  this.insert = function (obj) {
    this.datensatz.push(obj);
  };
  this.where = function (prop, val) {
    // Erweiterte Implementierung folgt gleich
    return this.datensatz;
  };
}

var db = new Database();

db.insert({
  name: "Jodie Foster",
  gender: "female",
  birthday: new Date(1962, 10, 19)
});

db.where();
// => [{ name: "Jodie Foster", … }]

Auf diese Weise besitzt jede Database-Instanz direkt die gewünschten Methoden und die Datensätze der Instanzen sind voneinander unabhängig.

Nun können die Methoden richtig implementiert werden. Zuerst die where-Methode: Um Objekte zu finden, die die übergebene Property auf den übergebenen Wert gesetzt haben, wird eine for-Schleife genutzt. Jedes Objekt, das dem Property-Value-Paar entspricht, wird dann einem Ergebnis-Array hinzugefügt, das die Funktion nach Vollendung der Schleife zurückgibt.

// Innerhalb der Konstruktorfunktion
this.where = function (prop, val) {
  // Iteriere über den Datensatz
  for (var resArr = [], i = 0; i < this.datensatz.length; i++) {
    // Prüfe ob das Objekt prop auf val gesetzt hat
    if (this.datensatz[i][prop] === val) {
      // Ist das der Fall, füge es dem Ergebnis-Array hinzu
      resArr.push(this.datensatz[i]);
    }
  }
  // Gebe das Ergebnis-Array zurück
  return resArr;
};

Wenn die Konstruktorfunktion verändert wird, muss jedes Mal eine neue Instanz erstellt werden, damit die Änderungen angewendet werden.
Da die neue Instanz auch einen neuen und somit leeren Datensatz beinhaltet, muss dieser auch jedes Mal erneut befüllt werden.

db = new Database();

db.insert({
  name: "Jodie Foster",
  gender: "female",
  birthday: new Date(1962, 10, 19)
});

db.where("gender", "female");
// => [{ name: "Jodie Foster", … }]

Außerdem soll die where-Methode den gesamten Datensatz zurückgeben, wenn keine Parameter übergeben wurden.

function Database () {
  …
  this.where = function (prop, val) {
    if (!prop) {
      // Falls where ohne Parameter aufgerufen wurde,
      // ist prop undefined. Wird es durch ein Ausrufe-
      // zeichen negiert, ist es true, sodass die if-
      // Bedingung erfüllt ist.
      // In dem Fall, gib den gesamten Datensatz zurück
      return this.datensatz;
    }
    …
  };
}

db = new Database();

db.insert({ … });

db.where();
// => [{ … }] // Gibt gesamten Datensatz zurück

Genau wie die where-Methode kann auch update implementiert werden. Hierbei iteriert genauso eine for-Schleife über den Datensatz, nur dass die übereinstimmenden Objekte nicht einem Ergebnis-Array hinzugefügt werden, sondern verändert werden.

function Database() {
  …
  this.update = function (whereProp, whereVal, updateProp, updateVal) {
    for (var i = 0; i < this.datensatz.length; i++) {
      if (this.datensatz[i][whereProp] === whereVal) {
        this.datensatz[i][updateProp] = updateVal;
      }
    }
  };
}

db = new Database();

db.insert({
  name: "Jodie Foster",
  gender: "male",
  birthday: new Date(1962, 10, 19)
});

// Upps, falsches Geschlecht
db.where("name", "Jodie Foster");
// => [{ name: "Jodie Foster", gender: "male", … }]

// Das kann mit update verändert werden
db.update("name", "Jodie Foster", "gender", "female");

// Nun ist Jodie Foster weiblich
db.where("name", "Jodie Foster");
// => [{ name: "Jodie Foster", gender: "female", … }]

// update kann auch Objekte erweitern
db.update("name", "Jodie Foster", "oscarWinner", true);

db.where();
// => [{ name: "Jodie Foster", …, oscarWinnder: true }]

Ebenfalls mit der gleichen Vorgehensweise wird die delete-Methode umgesetzt.
Die splice-Methode entfernt Elemente aus einem Array. Alle entfernten Elemente werden einem Ergebnis-Array hinzugefügt, das zurückgegeben wird.

function Database () {
  …
  this.delete = function (prop, val) {
    if (!prop) {
      // Wenn kein Parameter übergeben wurde,
      // lösche den gesamten Datensatz und
      // gib ihn zurück
      var tmp = this.datensatz; // Temporär speichern
      this.datensatz = []; // Datensatz überschreiben
      return tmp; // Bisherigen Datensatz zurückgeben
    }
    for (var resArr = [], i = 0; i < this.datensatz.length; i++) {
      if (this.datensatz[i][prop] === val) {
        // splice entfernt Element aus dem Array
        var entfernt = this.datensatz.splice(i, 1);
        resArr = resArr.concat(entfernt);
      }
    }
    return resArr;
  };
}

db = new Database();

db.insert({
  name: "Jodie Foster",
  gender: "female",
  birthday: new Date(1962, 10, 19),
  oscarWinner: true
});

// Entferne alle Oscar-Gewinner aus dem Datensatz
db.delete("oscarWinner", true);
// => [{ name: "Jodie Foster", …, oscarWinner: true }]

// Da der Datensatz nur aus einem Eintrag bestand und
// dieser gelöscht wurde, ist die Datenbank nun leer
db.where();
// => []

Die vollständige Konstruktorfunktion sieht damit folgendermaßen aus:

function Database() {
  this.datensatz = [];

  this.insert = function (obj) {
    this.datensatz.push(obj);
  };

  this.where = function (prop, val) {
    if (!prop) {
      return this.datensatz;
    }
    for (var resArr = [], i = 0; i < this.datensatz.length; i++) {
      if (this.datensatz[i][prop] === val) {
        resArr.push(this.datensatz[i]);
      }
    }
    return resArr;
  };

  this.update = function (whereProp, whereVal, updateProp, updateVal) {
    for (var i = 0; i < this.datensatz.length; i++) {
      if (this.datensatz[i][whereProp] === whereVal) {
        this.datensatz[i][updateProp] = updateVal;
      }
    }
  };

  this.delete = function (prop, val) {
    if (!prop) {
      var tmp = this.datensatz;
      this.datensatz = [];
      return tmp;
    }
    for (var resArr = [], i = 0; i < this.datensatz.length; i++) {
      if (this.datensatz[i][prop] === val) {
        var entfernt = this.datensatz.splice(i, 1);
        resArr = resArr.concat(entfernt);
      }
    }
    return resArr;
  };
}