JavaScript-Tutorium:Browser: Unterschied zwischen den Versionen

aus GlossarWiki, der Glossar-Datenbank der Fachhochschule Augsburg
Keine Bearbeitungszusammenfassung
 
(22 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 1: Zeile 1:
{{TBD}}
=Ziel=
=Ziel=


Zeile 5: Zeile 6:


=Einführung=
=Einführung=


==Native vs. Host==
==Native vs. Host==


In einer JavaScript-Umgebung existieren immer zwei unterschiedliche Arten von Objekten und Funktionen.
 
'''Native Objekte''' werden von der JavaScript-Engine zur Verfügung gestellt und sind durch den ECMAScript-Standard abgedeckt.
In einer JavaScript-Umgebung existieren zwei Arten von Objekte.
'''Host-Objekte''' sind hingegen nicht zwingend standardisiert und werden von der Umgebung bereitgestellt.
'''Native Objekte''' werden von der Engine bereitgestellt und sind durch den ECMAScript-Standard abgedeckt.
Im Browser handelt es sich bei Host-Objekten um Schnittstellen für den Zugriff auf den Browser selbst und externe Geräte.
'''Host-Objekte''' sind nicht immer standardisiert und werden von der Umgebung bereitgestellt.
Im Browser handelt es sich bei Host-Objekten um Schnittstellen für den Zugriff auf den Browser und externe Geräte.




==HTML5-Standard==
==HTML5-Standard==


Das bedeuteut, dass obwohl die Sprache selbst dem ECMAScript-Standard folgt, musste man bis vor wenigen Jahren
noch davon ausgehen dass sich die verfügbaren APIs je nach Browser sehr stark unterscheiden können.
Durch die steigende Entwicklungsgeschwindigkeit der Browser-Hersteller und den allgemein akzeptierten
[https://html.spec.whatwg.org/multipage/ HTML5-Standard] verbessert sich diese Situation jedoch zunehmend.


Heutzutage kann man davon ausgehen, dass jeweils die neuesten Versionen von Firefox, Chrome und Internet Explorer
Obwohl es den ECMAScript-Standard gibt, muss man davon ausgehen,
dass sich die verfügbaren APIs von Browser zu Browser unterscheiden können.
Durch die steigende Entwicklungsgeschwindigkeit der Browser-Hersteller und den
[https://html.spec.whatwg.org/multipage/ HTML5-Standard] verbessert sich diese Situation.
 
Heutzutage kann man davon ausgehen, dass jeweils die neuesten Versionen von Firefox und Chrome
die DOM-API und viele der neu verfassten HTML5-APIs bereits standardkonform unterstützen.
die DOM-API und viele der neu verfassten HTML5-APIs bereits standardkonform unterstützen.




==Inline Code vs. externe Dateien==
==Inline Code vs. externe Dateien==


Eine im Browser angezeigte Web-Seite besteht meist aus HTML, CSS und JavaScript, wobei nur HTML zwingend verlangt ist.
Eine im Browser angezeigte Web-Seite besteht meist aus HTML, CSS und JavaScript, wobei nur HTML zwingend verlangt ist.
Der JavaScript-Code kümmert sich normalerweise um die dynamischen Aspekte, wie Interaktionen und das Nachladen von Inhalten.
Der JavaScript-Code kümmert sich normalerweise um die dynamischen Aspekte, wie Interaktionen und das Nachladen von Inhalten.


Beim Besuch einer Website lädt der Browser das HTML-Dokument unter der angegeben URL herunter und evaluiert es.
Das Dokument enthält dann wiederum optional Verweise auf CSS-Dateien, Bilder, JavaScript-Dateien und andere Medien.
Neben Verweisen auf externen JavaScript-Code kann das Dokument auch Inline-Code-Blöcke enthalten.
Ob es sich um externe Dateien oder Inline-Code handelt wirkt sich auf die Ladezeiten und das Caching des Browser aus,
die enthaltenen Anweisungen und Ausdrücke werden jedoch nicht davon beeinflusst.


'''Anmerkung:'' Die Reihenfolge der eingebunden Code-Blöcke und angegebenen Dateiverweise ist allerdings wichtig,
Der Browser lädt ein HTML-Dokument unter der angegebenen URL herunter und evaluiert dieses.
Das Dokument enthält optional Verweise auf CSS-Dateien, Bilder, JavaScript-Dateien und andere Medien.
Neben Verweisen auf externen JavaScript-Code kann das Dokument auch Inline-Code enthalten.
Ob es sich um externe Dateien oder Inline-Code handelt wirkt sich auf die Ladezeiten aus,
der Code selbst wird davon nicht beeinflusst.
 
 
'''Anmerkung:''' Die Reihenfolge der eingebunden Code-Blöcke und angegebenen Dateiverweise ist wichtig,
da diese ebenso die Reihenfolge der Code-Ausführung darstellt.
da diese ebenso die Reihenfolge der Code-Ausführung darstellt.


==JavaScript-Kontext==
==JavaScript-Kontext==


Für jede geladene Web-Seite, unabhängig davon ob sie im separaten Fenster oder als ein weiterer Tab angezeigt wird,
erzeugt der Browser einen separaten und isolierten JavaScript-Kontext, in dem der geladene Code ausgeführt wird.
Das bedeutet dass alle Objekte und Funktionen nur so lange am Leben sind bis die Seite neu geladen oder verlassen wird.
Ein Zugriff auf andere Seiten in anderen Fenstern oder Tabs ist aus Sicherheitsgründen nicht erlaubt.


Für jede Web-Seite, unabhängig davon ob sie im separaten Fenster oder als Tab angezeigt wird,
erzeugt der Browser einen separaten isolierten JavaScript-Kontext, in dem Code ausgeführt wird.
Das bedeutet, dass alle Objekte und Funktionen nur so lange existieren bis die Seite neu geladen oder verlassen wird.
Ein Zugriff auf andere Seiten ist aus Sicherheitsgründen nicht erlaubt.


=Window=
=Window=
Zeile 137: Zeile 145:
<source lang="javascript">
<source lang="javascript">
console.log(location.href);
console.log(location.href);
console.log(location.toString()
console.log(location.toString());
</source>
</source>


Zeile 173: Zeile 181:


Das globale <code>history</code>-Objekt ermöglicht den programmatischen Zugriff auf den Browser-Verlauf.
Das globale <code>history</code>-Objekt ermöglicht den programmatischen Zugriff auf den Browser-Verlauf.
Es werden Funktionen bereitgestellt um im Verlauf beliebig viele Schritte zurück und vorwärts zu gehen.
Es werden Funktionen zur Verfügung gestellt um im Verlauf beliebig viele Schritte zurück und vorwärts zu gehen.


<source lang="javascript">
<source lang="javascript">
Zeile 183: Zeile 191:


'''Anmerkung:''' Aus Sicherheitsgründen ist es nicht erlaubt auf Informationen einzelner Einträge im Verlauf zuzugreifen.
'''Anmerkung:''' Aus Sicherheitsgründen ist es nicht erlaubt auf Informationen einzelner Einträge im Verlauf zuzugreifen.


==Navigator==
==Navigator==
Zeile 200: Zeile 206:
=Document Object Model=
=Document Object Model=


Im Browser können einem Fenster beliebig viele Dokumentenobjekte zugeordnet werden, mindestens aber ein Hauptdokument.  
 
Das automatisch erstellte Hauptdokument wird über die globale Variable <code>document</code> zur Verfügung gestellt.
Einem <code>window</code>-Objekt können beliebig viele Dokumentenobjekte zugeordnet werden, mindestens aber ein Hauptdokument.
Das Hauptdokument wird über die globale Variable <code>document</code> zur Verfügung gestellt.
Die Schnittstelle für den Dokumentenzugriff wird durch das [http://www.w3.org/DOM/ Document Object Model] spezifiziert.
Die Schnittstelle für den Dokumentenzugriff wird durch das [http://www.w3.org/DOM/ Document Object Model] spezifiziert.


==Objektmodell==
==Objektmodell==


Ein <code>Document</code>-Objekt repräsentiert ein vom Browser interpretiertes HTML-Dokument als Objektmodell mit einer Baumstruktur.
Ein <code>Document</code>-Objekt repräsentiert ein vom Browser interpretiertes HTML-Dokument als Objektmodell mit einer Baumstruktur.
Zeile 220: Zeile 229:
     </p>
     </p>
     <input type="text" value="Eingabe">
     <input type="text" value="Eingabe">
   
 
   </body>
   </body>
</html>
</html>
Zeile 226: Zeile 235:




Das oben dargestellte Markup wird bei der Interpretation durch den Browser in folgendes Objektmodell übersetzt:
Das Markup wird in folgendes Objektmodell übersetzt:
 


* Ein <code>html</code>-Knoten mit den Unterknoten <code>head</code> und <code>body</code>
* Ein '''html'''-Knoten mit den Unterknoten '''head''' und '''body'''
* Ein <code>head</code>-Knoten mit dem Unterknoten <code>title</code>
* Ein '''head'''-Knoten mit dem Unterknoten '''title'''
* Ein <code>title</code>-Knoten mit einem Text-Unterknoten
* Ein '''title'''-Knoten mit einem Text-Unterknoten
* Ein <code>body</code>-Knoten mit den Unterknoten <code>p</code> und <code>input</code>
* Ein '''body'''-Knoten mit den Unterknoten '''p''' und '''input'''
* Ein <code>p</code>-Knoten mit einem Text-Unterknoten und einem Kommentar-Unterknoten
* Ein '''p'''-Knoten mit einem Text- und einem Kommentar-Unterknoten
* Ein <code>input</code>-Knoten
* Ein '''input'''-Knoten


'''Tipp:''' Achtet man bei HTML auf eine korrekte Einrückung so ist das resultierende Objektmodell meist klar erkennbar.
'''Tipp:''' Achtet man bei HTML auf eine korrekte Einrückung so ist das resultierende Objektmodell meist gut erkennbar.




===Markup-Korrekturen===
===Markup-Korrekturen===


Dabei ist wichtig zu beachten, dass es sich bei dem Objektmodell '''nicht''' um das originale Markup handelt.
Bei dem Objektmodell handelt es sich '''nicht''' um exaktes Abbild des originalen Markups.
Der Unterschied wird besonders deutlich wenn man das Resultat von folgendem invalidem HTML untersucht.
Der Unterschied wird klar, wenn man das Resultat von invalidem HTML untersucht.


<source lang="html4strict">
<source lang="html4strict">
Zeile 259: Zeile 267:
</source>
</source>


Das Objektmodell fügt zwischen dem <code>table</code>- und dem <code>tr</code>-Knoten einen <code>tbody</code>-Knoten ein damit das HTML valide wird.
Der Browser fügt zwischen dem <code>table</code>- und dem <code>tr</code>-Knoten einen <code>tbody</code>-Knoten ein.


'''Tipp:''' Im Browser kann man das Objektmodell einer Seite mit dem (DOM) Inspector der Entwicklerwerkzeuge betrachten.


==Aufbau von Dokumenten==


==Aufbau von Dokumenten==


===Node-Objekte===
===Node-Objekte===


Jeder Knoten innerhalb eines Dokumentenobjekts ist direkt oder indirekt abgeleitet vom <code>Node</code>-Konstruktor/-Prototypen.
Jeder Knoten innerhalb eines Dokumentenobjekts ist direkt oder indirekt abgeleitet vom <code>Node</code>-Konstruktor/-Prototypen.
Mit Ausnahme des Dokumentenobjekts selbst wird jedem Knoten ein Dokument zugeordnet durch die <code>ownerDocument</code>-Eigenschaft.
Mit Ausnahme des Dokumentenobjekts selbst wird allen Knoten ein Dokument zugeordnet durch die <code>ownerDocument</code>-Eigenschaft.
Des Weiteren hat jeder Knoten einen bestimmten numerischen Typen, welcher in der Eigenschaft <code>nodeType</code> gespeichert wird.
 
Die folgende Liste enthält die gängigsten Typen von Dokumentenknoten denen man bei der Arbeit mit Dokumenten begegnet:
 
Des Weiteren hat jeder Knoten einen numerischen Typen, der in der Eigenschaft <code>nodeType</code> gespeichert wird.
Die gängisten Typen von Dokumentenknoten sind:


* 1: ELEMENT_NODE
* 1: ELEMENT_NODE
Zeile 278: Zeile 288:
* 9: DOCUMENT_NODE
* 9: DOCUMENT_NODE


Eine vollständige Referenz der Eigenschaften von Node-Objekten finden Sie auf  
Eine vollständige Referenz der Eigenschaften von Node-Objekten finden Sie auf
[https://developer.mozilla.org/en-US/docs/Web/API/Node dieser] MDN-Seite.
[https://developer.mozilla.org/en-US/docs/Web/API/Node dieser] MDN-Seite.


===Document-Objekte===
===Document-Objekte===


Ein Dokument wird mit dem <code>Document</code>-Konstruktor erzeugt und ist selbst keinem Dokument, jedoch einem Fenster zugeordnet.
Das Dokument selbst repräsentiert ein abstraktes Dokument und somit kein HTML-Element.
 
Es verweist aber auf das <code>html</code>-Element mit der Eigenschaft <code>documentElement</code>.
<source lang="javascript">
console.log(document.defaultView);
console.log(window == document.defaultView);
</source>
 
Das Dokument selbst repräsentiert kein HTML-Element, verweist aber auf das <code>html</code>-Element mit der Eigenschaft <code>documentElement</code>.
 


<source lang="javascript">
<source lang="javascript">
Zeile 297: Zeile 301:
</source>
</source>


Mithilfe der Funktionen eines Dokuments ist es möglich neue <code>Element</code>-Objekte zu erstellen, einzufügen und diese zu suchen.
Mithilfe eines Dokuments ist es möglich neue <code>Element</code>-Objekte zu erstellen, einzufügen und diese zu suchen.




===Element-Objekte===
===Element-Objekte===


Jedes HTML-Tag wird in einen Knoten übersetzt welcher mit dem <code>Element</code>-Konstruktor erzeugt wird.
Jedes HTML-Tag wird in einen Knoten übersetzt, welcher mit dem <code>Element</code>-Konstruktor erzeugt wird.
Element-Objekte besitzen Schnittstellen um auf Eigenschaften eines HTML-Tags zuzugreifen und diese zu modifizieren.
Element-Objekte besitzen Schnittstellen um auf Eigenschaften des Tags zuzugreifen und diese zu modifizieren.


<source lang="javascript">
<source lang="javascript">
Zeile 313: Zeile 317:




===Comment-Objekte===
==Erstellen von Elementen==


HTML-Kommentare werden in Comment-Objekte umgewandelt welche mit dem <code>Comment</code>-Konstruktor erzeugt werden.


Neue Element-Objekte werden über die Funktion <code>createElement()</code> eines <code>document</code>-Objekts unter Angabe des Tag-Namens erzeugt.


===Text-Objekte===
Texte werden in <code>Text</code>-Objekte umgewandelt, wobei auf diese auch durch übergeordnete Elemente zugegriffen werden kann.
Normalerweise wird bei der Anwendungsprogrammierung mit JavaScript nicht direkt mit Text-Knoten gearbeitet.
==Erstellen von Elementen==
Neue Element-Objekte werden über die Schnittstelle eines <code>Document</code>-Objekts unter der Angabe eines Tag-Namens erzeugt.
 
<source lang="javascript">
<source lang="javascript">
var absatz = document.createElement('p');
var absatz = document.createElement('p');
Zeile 334: Zeile 328:
</source>
</source>


Bei der Erstellung der Elemente wird das augerufene Dokument als zugehöriges Dokument dem neu erstellten Objekt zugewiesen.
 
Bei der Erstellung wird das neue Element dem augerufenen Dokument durch die Eigenschaft <code>ownerDocument</code> als zugehörig markiert.


<source lang="javascript">
<source lang="javascript">
Zeile 341: Zeile 336:
</source>
</source>


Da die Schnittstelle eines Dokuments nicht mit HTML-Standards gekoppelt ist können beliebige Tag-Namen vergeben werden.
 
Da die Schnittstelle eines Dokuments nicht mit HTML-Standards gekoppelt ist, können beliebige Tag-Namen vergeben werden.


<source lang="javascript">
<source lang="javascript">
Zeile 347: Zeile 343:
console.log(meinElement);
console.log(meinElement);
</source>
</source>


==Suchen von Elementen==
==Suchen von Elementen==


Besonders wichtig bei der Arbeit mit Dokumenten ist die Suche von einzelnen oder mehreren Elementen.
Besonders wichtig bei der Arbeit mit Dokumenten ist die Suche von einzelnen oder mehreren Elementen.
Die Schnittstellen für die Suche im Browser hat sich über die letzten Jahre rapide verbessert.
Aufgrund dessen haben sich die Schnittstellen für die Suche im Browser über die letzten Jahre rapide verbessert.




===Vorgefertige Kriterien===
===Vorgefertige Kriterien===


Die Suche nach einem eindeutigen Element kann mithilfe einer ID ausgeführt werden, insofern das Element eine besitzt.
Die ursprünglichen Funktionen zum Suchen von Elementen erlauben wenige festgelegte Kriterien.
 
<source lang="javascript">
var spielfeld = document.getElementById('spielfeld');
console.log(spielfeld);
 
var divElemente = document.getElementsByTagName('div');
console.log(divElemente);
 
var gegnerElemente = document.getElementsByClassName('gegner');
console.log(gegnerElemente.length);
</source>
 
Auf diese Weise können Elemente anhand der ID, Klasse oder des Tag-Namen gesucht werden.
Der Rückgabewert ist bei der ID-Suche ein <code>Node</code>-Objekt und bei den anderen eine <code>NodeList</code>, ein Array-artiges Objekt von Nodes.
 
 
===CSS-Selektoren===
 
 
Die Suche mithilfe von vorgefertigen Kriterien hat deutliche Limitierungen.
Deswegen enstand durch Bibliotheken wie jQuery das Konzept von Query-Selektoren, welche als
[http://www.w3.org/TR/CSS2/selector.html CSS-Selektoren] standardisiert werden und in vielen Browser verfügbar sind.
 
 
Eine CSS-Selektorgruppe beschreibt beliebig viele Kriterien, die auf ein oder mehrere HTML-Elemente zutreffen.
Der Name wurde so gewählt, da diese ebenso in Stylesheets genutzt werden um Elemente auszuwählen.
Aufgrund der Kombinierbarkeit von CSS-Selektoren ergibt sich eine sehr mächtige Suchfunktionalität.
 
 
Es gibt zwei Funktionen für die Suche mit CSS-Selektoren:
 
* <code>document.querySelector()</code> liefert das erste Element zurück welches die Kriterien erfüllt
* <code>document.querySelectorAll()</code> liefert alle Elemente zurück welche die Kriterien erfüllen in Form einer <code>Node</code>-Liste
 
 
====Mögliche Kriterien====
 
 
Das einfachste Kriterium für einen CSS-Selektor ist die Angabe eines Tag-Namen.
 
<source lang="javascript">
var absaetze = document.querySelectorAll('p');
console.log(absaetze);
</source>
 
 
Die Angabe einer ID eines HTML-Elements erfolgt durch eine vorangestellte Raute (<code>#</code>).
 
<source lang="javascript">
var spielfeldElement = document.querySelector('#spielfeld');
console.log(spielfeldElement);
</source>
 
 
Klassen-Selektoren werden durch einen vorangestellten Punkt (<code>.</code>) gekennzeichnet.
 
<source lang="javascript">
var gegnerElemente = document.querySelectorAll('.gegner');
console.log(gegnerElemente);
</source>
 
 
Die Mächtigkeit von CSS-Selektoren ergibt sich vor allem durch die beliebige Kombinationsmöglichkeiten von Selektoren.
 
<source lang="javascript">
var schwierigeGegnerElemente =
  document.querySelectorAll('.gegner.schwierig');
console.log(schwierigeGegnerElemente);
</source>
 
 
'''Anmerkung 1:''' Die Komplexität einer CSS-Selektorgruppe beeinflusst die Performance einer Suche.
 
 
'''Anmerkung 2:''' Eine vollständige Erklärung der Funktionsweise von CSS-Selektoren ist nicht Ziel dieses Tutoriums.
Diese Einführung dient lediglich dazu ein grundsätzliches Verständnis für den Umgang mit Selektoren zu vermitteln.
 
 
==Hinzufügen von Elementen==
 
 
Neu erstellte Elemente können im Dokument als untergeordnete Knoten mithilfe von <code>appendChild()</code> eingefügt werden.
 
<source lang="javascript">
var spielerElement = document.createElement('div');
document.documentElement.appendChild(spielerElement);
</source>
 
 
Der künftig übergeordnete Knoten muss angesprochen werden, wenn das Element an einer bestimmten Stelle landen soll.
 
<source lang="javascript">
var spielerElement = document.querySelector('div');
var spielfeld = document.createElement('div');
spielfeld.appendChild(spielerElement);
</source>
 
 
==Modifizieren von Elementen==
 
 
Mithilfe der entsprechenden DOM-Schnittstellen können Text, HTML, Attribute sowie das Styling von Elementen beeinflusst werden.
 
 
===Ändern von Text und HTML===
 
 
Der gesamte HTML-Inhalt eines Elements kann mithilfe der Eigenschaft <code>innerHTML</code> beeinflusst werden.
 
<source lang="javascript">
var body = document.querySelector('body');
body.innerHTML = 'Hallo Welt';
</source>
 
 
Abseits von einfachen Text-Änderungen lässt sich sogar das gesamte enthaltene HTML beeinflussen.
Anstatt also mit der DOM-API einzelne Elemente zu erzeugen kann einfach Markup als Wert zugewiesen werden.


<source lang="javascript">
<source lang="javascript">
var inhalt = document.getElementById('inhalt');
var body = document.querySelector('body');
console.log(inhalt);
body.innerHTML = '<p>Hallo <strong>Welt!</strong></p>';
</source>
</source>


Bei einer Suche wird entweder das Dokument selbst oder aber ein beliebiges Element als Ausgangspunkt verwendet.
 
===Ändern von Attributen===


Für den Zugriff auf die Attribute eines HTML-Elements gibt es die Funktionen <code>getAttribute()</code> und <code>setAttribute()</code>.
Beide akzeptieren als erstes Argument den Namen des Attributs, der Setter akzeptiert außerdem einen zu setzenden Wert.


<source lang="javascript">
<source lang="javascript">
var inhalt = document.getElementById('inhalt');
var spielerElement = document.createElement('div');
var kommentare = inhalt.getElementById('kommentare');
spielerElement.innerHTML = '_|_';
spielerElement.setAttribute('class', 'spieler');
document.querySelector('body').appendChild(spielerElement);
 
console.log(spielerElement.getAttribute('class'));
</source>
</source>


Werden Elemente einer bestimmten Art gesucht so kann der Tag-Name oder aber die HTML-Klasse als Kriterium genutzt werden.
 
'''Anmerkung:''' Besonders häufige Attributsänderungen sind das Hinzufügen und/oder Entfernen von Klassen.
Dafür gibt es in neueren Browsern die Liste <code>classList</code> auf jedem <code>Element</code>-Objekt, welche die Arbeit erleichtert.


<source lang="javascript">
<source lang="javascript">
var elemente = document.getElementsByTagName('body');
var spielerElement = document.querySelector('.spieler');
console.log(elemente);
spielerElement.classList.add('unbesiegbar');
</source>
 
 
===Ändern von CSS-Eigenschaften===
 
 
Neben dem Document Object Model existiert außerdem der [http://dev.w3.org/csswg/cssom/ CSS Object Model Standard]
welcher die Schnittstelle für den Zugriff auf CSS-Eigenschaften von DOM-Objekten standardisiert.
 
 
Ein jedes DOM-Element vom Typ <code>Element</code> besitzt eine Eigenschaft <code>style</code> welche alle CSS-Eigenschaften beherbergt.


var kommentare = document.getElementsByClassName('kommentar');
<source lang="javascript">
console.log(kommentare.length);
var body = document.querySelector('body');
console.log(body.style.color);
console.log(body.style.padding);
</source>
</source>


Bei dieser Art von Suche wird eine <code>NodeList</code> als Ergebnis zurückgeliefert, ein Array-artiges Objekt von Node-Elementen.


Einzelne Elemente dieser Liste können entweder über eine Funktion oder aber über einen Index abgefragt werden.
Diese Eigenschaften spiegeln die in Stylesheets definierten Regeln wieder, jedoch inklusive dem kaskadierenden Charakter.
Das heisst dass ein Element-Objekt nicht zwingend selbst eine <code>color</code> besitzt, diese jedoch für die Anzeige vererbt bekommt.
 
 
Die Eigenschaften können ebenso beliebig modifiziert werden, was zu einer sofortigen Anpassung der Darstellung führt.


<source lang="javascript">
<source lang="javascript">
var absaetze = document.getElementsByTagName('p');
var body = document.querySelector('body');
console.log(absaetze);
body.style.backgroundColor = '#AAF';
console.log(absaetze.item(0));
console.log(absaetze.item(0) == absaetze[0]);
</source>
</source>




===Verweise===
==Löschen von Elementen==
 
 
Ebenso wie neue Elemente einem Dokument hinzugefügt werden können, so können diese oder bestehende auch gelöscht werden.
Dazu muss man den übergeordneten Knoten ansteuern und mithilfe von <code>removeChild()</code> die Entfernung durchführen.
 
<source lang="javascript">
var spielfeld = document.querySelector('.spielfeld');
var spielerElement = spielfeld.querySelector('.spieler');
spielfeld.removeChild(spielerElement);
</source>
 


Jedes Element-Objekt verweist selbst auf seine übergeordneten und untergeordneten Knoten sowie seine Nachbaren.
Ist einem der übergeordnete Knoten nicht bekannt so kann diesen mithilfe der Knotenverweise ermitteln.


<source lang="javascript">
<source lang="javascript">
var body = document.getElementsByTagName('body').item(0);
var entferneKnoten = function(knoten) {
for (var i = 0; i < body.childNodes.length; i++) {
  if (!knoten.parentNode) {
   console.log(body.childNodes[i]);
    throw new Error('no parent node');
  }
   knoten.parentNode.removeChild(knoten);
}
}
console.log(body.parentNode);
 
var spielerElement = document.querySelector('.spieler');
entferneKnoten(spielerElement);
</source>
 
=DOM Events=
 
 
==Ereignisorientierte Programmierung==
 
 
Für die Verarbeitung von stets verfügbaren Informationen eignet sich ein sequentieller Ablauf von Befehlen.
Ergeben sich Informationen jedoch dynamisch zur Laufzeit kann die ereignisorientierte Programmierung genutzt werden.
 
Hierbei wird nicht aktiv auf eine Information gewartet, das Programm wird benachrichtigt wenn ein Ereignis eintritt.
Somit gibt es nicht nur einen Kontrollfluss, sondern beliebig viele, welche sogar parallel ablaufen können.
 
 
==Ereignisse im Browser==
 
 
Bei der Interaktion mit Websites werden viele Ereignisse ausgelöst.
Zuerst wird die HTML-Seite geladen und gerendert, danach geschieht dies für alle referenzierten Inhalte.
Diese Vorgänge lösen Ereignisse aus, welche durch den Browser selbst verursacht werden.
 
Zum Beispiel gibt es Ereignisse für Medien, welche beschreiben, dass diese fertig geladen wurden.
 
 
Sobald der Browser beginnt eine Web-Seite zu rendern können Ereignisse durch den Benutzer ausgelöst werden.
Fast jede Interaktion löst ein Ereignis aus, welches wiederum in bestimmten Reaktionen resultiert.
 
'''Beispiele:'''
Klickt der Benutzer einen Link so ist das ein Ereignis, welches zur Folge hat dass die eine URL geladen wird.
Wird ein Scroll-Balken bewegt ergibt sich daraus ein Ereignis, welches den Browser scrollen lässt.
 
 
Zu den wichtigsten Arten von durch Benutzer ausgelöste Ereignissen zählen:
Mausbewegungen und -klicks, Tastatureingaben und Scrolling.
 
Eine vollständige Auflistung von Ereignissen finden Sie auf [https://developer.mozilla.org/en-US/docs/Web/Events dieser] MDN-Seite.
Nebem dem Benutzer gibt es noch andere Auslöser wie zum Beispiel verbundene Server.
 
 
==Aufbau eines Ereignisses==
 
 
Ein im Browser auftretendes Ereignis besitzt immer drei folgende Merkmale:
 
* '''Ereignis-Name:''' eine Zeichenkette die das Ereignis in der Gegenwart beschreibt, z.B. "click" oder "mousemove"
* '''Ereignis-Daten:''' ein Datenobjekt mit einer pro Ereignis festgelegten Struktur
* '''Ereignis-Ziel:''' ein Objekt das als Ziel des Ereignis gilt; in der Regel handelt es sich um DOM-Elemente
 
Beispiel: Eine '''Tastatureingabe''' in einem '''Formularfeld''', bei dem das Zeichen '''@''' eingegeben wird.
 
 
==Phasen der Ereignispropagierung==
 
 
Handelt es sich bei dem ursprünglichen Ziel eines Ereignisses um ein DOM-Objekt werden in der Regel drei Phasen durchlaufen.
 
 
===Capturing-Phase===
 
In Phase 1, der '''Capturing-Phase''', wird ein Ereignis mit dem obersten Dokumentenknoten als Ziel ausgelöst.
Dabei handelt es sich in der Regel um das <code>document</code>-Objekt, welches alle andere Nodes als Unterknoten enthält.
Es werden dann schrittweise Ereignisse ausgelöst für jeden Unterknoten auf dem Pfad zum ursprünglichen Ereignisziel.
 
</source>
+ window      - Ereignis mit window als aktuelles Ziel (optional)
  + document  - Ereignis mit body als aktuelles Ziel
    + body    - Ereignis mit body als aktuelles Ziel
      + p    - ursprüngliches Ziel
</source>
</source>


Diese Referenzen können genutzt werden um sich durch das Dokument zu bewegen und bestimmte Elemente zu finden.
'''Anmerkung:''' Bei manchen Ereignissen ist <code>window</code> das oberste Objekt in der Hierarchie.
 
 
===Target-Phase===
 
Ist der Browser beim ursprünglichen Ziel angelangt wird in Phase 2 gewechselt, der sogenannten '''Target-Phase'''.
Dabei wird ein einzelnes Ereignis ausgelöst, für welches das ursprüngliche DOM-Objekt als Ziel verwendet wird.
 
 
===Bubbling-Phase===
 
Zum Abschluss der Ereignispropagierung wechselt der Browser in Phase 3, der sogenannten '''Bubbling-Phase'''.
Hierbei wird der Pfad der Capturing-Phase in umgekehrter Richtung durchlaufen und jeweils wieder ein Ereignis ausgelöst.
 
'''Anmerkung:''' In Bibliotheken wie jQuery hat man nur Zugriff auf die Bubbling-Phase, nicht auf die Capturing-Phase.
 
 
===Browser-Unterschiede===
 
Capturing und Bubbling bilden die Tatsache ab, dass wenn ein Ereignis auf einem Element stattfindet,
dieses Ereignis ebenso für die übergeordneten Elemente gültig ist.
'''Beispiel:''' Wird ein Mensch in einem Krankenhaus geboren, so wird er auch in einer Stadt und in einem Land geboren.
 
Einer der beiden Phasen wäre ausreichend für die Ereignisvearbeitung.
Aufgrund von unterschiedlichen Browser-Implementierungen existieren heutzutage jedoch beide gleichzeitig.
 
 
===Globale Ereignisse===
 
Beschreibt ein Ereignis ein globales Geschehen wird oftmals das <code>document</code> oder <code>window</code>-Objekt als Ereignisziel verwendet.
In diesen Fällen ist es möglich dass weder Capturing- noch eine Bubbling-Phase durchlaufen werden.
 
Beispiel: Das Ergeinis <code>DOMContentLoaded</code> beschreibt, dass eine Seite (ohne seine externen Medien) fertig geladen wurde.
 
 
==Ereignisverarbeitung==
 
 
Mithilfe der DOM-Schnittstelle erhält man die Möglichkeit Ereignisse selbst zu verarbeiten und darauf zu reagieren.
 
 
===Anmeldung===
 
 
Die Funktion <code>addEventListener</code> kann auf jedem DOM-Objekt, dem <code>document</code>-Objekt und dem <code>window</code>-Objekt aufgerufen werden.
Dabei ist das Objekt auf dem die Funktion aufgerufen das '''Ziel''' des Ereignisses, welches verarbeiten werden soll.
 
 
Die Funktion akzeptiert die folgenden drei Argumente:
* den '''Namen des Ereignisses''' das man verarbeiten möchte in Form eines Strings
* die '''Callback-Funktion''' welche das Ereignis-Objekt erwartet und den Code zur Verarbeitung enthält
* ein optionales '''Flag''' das bestimmt ob das Ereignis in der Capturing- oder Bubbling-Phase verarbeitet werden soll
 
Standardmäßig ist das Flag <code>false</code>, was bedeutet dass das Ereignis in der Bubbling-Phase verarbeitet wird.
 
 
Es wird für ein Ereignis auf einem Ziel ein Event-Lister in Form einer Funktion angemeldet.
Beim Eintreten des Ereignisses wird der sogenannte Callback aufgerufen und das entsprechende Ereignis-Objekt übergeben.


<source lang="javascript">
<source lang="javascript">
var container = [];
var seiteWurdeGeladen = function(event) {
var absaetze = document.getElementsByTagName('p');
  console.log('Die Seite ist nun fertig geladen');
for (var i = 0; i < absaetze.length; i++) {
   console.log(event);
   container.push(absaetze[i].parentNode);
}
}
console.log(container);
document.addEventListener('DOMContentLoaded', seiteWurdeGeladen);
</source>
 
<source lang="javascript">
var body = document.querySelector('body');
body.innerHTML = '<a id="link" href="#">Link</a>';
var link = document.querySelector('#link');
var linkWurdeGeklickt = function(event) {
  console.log('Der Link wurde geklickt');
};
link.addEventListener('click', linkWurdeGeklickt);
</source>
 
 
Aufgrund der Phasen der Ereignispropagierung kann man Ereignisse global verarbeiten, unabhängig davon wo sie stattgefunden haben.
 
<source lang="javascript">
var body = document.querySelector('body');
body.innerHTML = '<a href="#">Link 1</a><br><a href="#">Link 2</a>';
var irgendwoWurdeGeklickt = function(event) {
  console.log('Benutzer hat irgendwo auf der Seite geklickt');
};
document.addEventListener('click', irgendwoWurdeGeklickt);
</source>
 
 
===Callbacks===
 
 
Ein Callback ist eine Rückruffunktion, welche an ein andere Funktion übergeben wird und
zu späteren Zeitpunkten ggf. ausgeführt wird.
 
<source lang="javascript">
var mausWurdeBewegt = function(event) {
  console.log('Benutzer hat die Maus bewegt');
};
document.addEventListener('mousemove', mausWurdeBewegt);
</source>
 
 
Es ist jedoch nicht garantiert dass eine übergebene Callback-Funktion überhaupt jemals ausgeführt wird.
 
<source lang="javascript">
var esIstOktober = function(event) {
  console.log('Es ist Oktober');
};
 
var istEsOktober = function(callback) {
  if (new Date().getMonth() == 9) callback();
};
 
istEsOktober(esIstOktober);
</source>
 
 
===Ereignis-Objekte===
 
 
Jedem Event-Listener-Callback wird beim Aufruf ein für ein Ereignis spezifisches Ereignis-Objekt übergeben.
 
<source lang="javascript">
document.addEventListener('click', function(event) {
  console.log(event);
  console.log(event.screenX);
  console.log(event.screenY);
  console.log(event.button);
});
</source>
 
 
Dieses Objekt enthält neben spezifischen auch generelle Ereignisinformationen wie zum Beispiel das Ereignis-Ziel.
 
<source lang="javascript">
document.addEventListener('click', function(event) {
  console.log(event.target);
});
</source>
 
 
Hinsichtlich der drei Event-Phasen unterscheidet man zwischen ursprünglichem und aktuellem Ziel.
Das aktuelle Ziel ist das für welches das Ereignis aktuell gilt und das ursprüngliche das wo es stattgefunden hat.
 
<source lang="javascript">
document.addEventListener('click', function(event) {
  console.log(event.target); // ursprüngliches Ziel
  console.log(event.currentTarget); // aktuelles Ziel
});
</source>
 
Das aktuelle Ziel <code>currentTarget</code> entspricht also immer dem Objekt auf dem man den Event-Handler angemeldet hat.
 
 
===Abmeldung===
 
Mithilfe der Funktion <code>removeEventListener</code> kann ein angemeldeter Event-Listener wieder entfernt werden.
Diese Funktion nur zwei Argumente: Den Ereignis-Namen und die Event-Listener-Funktion.
 
<source lang="javascript">
var mausWurdeBewegt = function(event) {
  console.log(event);
};
 
document.addEventListener('mousemove', mausWurdeBewegt);
document.removeEventListener('mousemove', mausWurdeBewegt);
</source>
 
 
===Standardverhalten===
 
 
Nachdem alle Ereignisse verarbeitet wurden führt der Browser gegegebenfalls eine "Default Action" aus.
Eine solche Aktion ist das Standardverhalten des Browsers als Reaktion auf das eingetretene Ereignis.
 
Beispiele:
* Benutzer klickt auf einen Link -> der Browser lädt die URL
* Benutzer führt Tastatureingaben in einem Feld aus -> der Browser fügt die Zeichen dem Feld hinzu
 
 
Innerhalb einer Event-Listener-Funktion ist es möglich mithilfe von <code>preventDefault()</code> das Standardverhalten zu unterdrücken.
 
<source lang="javascript">
    document.addEventListener('click', function(event) {
      event.preventDefault();
    });
</source>
</source>


Der oben dargestellte Code führt dazu, dass kein Klick auf einen Link die enthaltene URL aufruft.


===CSS-Selektoren===


Die Suche mithilfe von vorgefertigen Kriterien und Verweisen auf benachbarte Elemente hat gewisse Limitierungen.
==Wichtige Events==
Aufgrund dessen enstand durch bekannte Bibliotheken wie jQuery das Konzept von Query-Selektoren, welche heute als  
 
[http://www.w3.org/TR/CSS2/selector.html CSS-Selektoren] standardisiert werden und in vielen Browser verfügbar sind.
 
Die folgenden Ereignisse sind die wichtigsten in der täglichen browser-seitingen Anwendungsentwicklung mit JavaScript.
 
 
===DOMContentLoaded===
 
Dieses Ereignis beschreibt, dass das HTML geladen und interpretiert wurde und das DOM und CSSOM erzeugt und gerendert wurde.
Es wird keine Aussage darüber gemacht ob weitere externe Inhalte bereits geladen wurden, wie z.B. Bilder.
Das Ereignis wird ausschliesslich auf dem <code>document</code>-Objekt als Ereignisziel ausgelöst.
 
<source lang="javascript">
document.addEventListener('DOMContentLoaded', function(event) {
  console.log('Mit der Seite kann interagiert werden');
});
</source>


Ein CSS-Selektorgruppe beschreibt eine beliebige Menge an Kriterien, die auf ein oder mehrere HTML-Elemente zutrifft.
Der Name wurde so gewählt da CSS-Selektoren ebenso in Stylesheets genutzt werden um eine Menge an Elementen auszuwählen.
Aufgrund der Kombinierbarkeit einzelner CSS-Selektoren ergibt sich eine sehr mächtige Suchfunktionalität.


Im Browser gibt zwei verschiedene Funktionen um Elemente mithilfe von CSS-Selektoren zu suchen:
===load===


* <code>document.querySelector</code> liefert das erste Element zurück welches die Kriterien erfüllt
Dieses Ereignis besagt, dass die gesamte Web-Seite inklusive aller externen referenziereten Inhalte gerendert wurden.
* <code>document.querySelectorAll</code> liefert alle Elemente zurück welche die Kriterien erfüllen
Im Gegensatz zu <code>DOMContentLoaded</code> wird dieses Ereignis ausschliesslich auf dem <code>window</code>-Objekt ausgelöst.


<source lang="javascript">
window.addEventListener('load', function(event) {
  console.log('Die Seite wurde vollständig geladen');
});
</source>


====Mögliche Kriterien====


Das einfachste Kriterium für einen CSS-Selektor ist die Angabe eines Tag-Namen.
===click===
 
Jedes Mal wenn der Benutzer (mit der Maus) auf ein sichtbares DOM-Element klickt wird dieses Ereignis ausgelöst.
Bei der Propagierung dieses Ereignisses werden wie alle drei Ereignisphasen (Capturing, Bubbling, Target) durchlaufen.


<source lang="javascript">
<source lang="javascript">
var absaetze = document.querySelectorAll('p');
document.addEventListener('click', function(event) {
console.log(absaetze);
  console.log('Der Benutzer hat auf etwas geklickt');
});
</source>
</source>


Die Angabe einer ID eines HTML-Elements erfolgt durch eine vorangestellte Raute.
 
===mousemove, mouseover, mouseout===
 
Bei Mausbewegungen über DOM-Element wird konstant das Ereignis <code>mousemove</code> mit den entsprechenden Koordinaten ausgelöst.
Das Ereignis <code>mouseover</code> wird erzeugt wenn die Maus in ein Element eindringt, <code>mouseout</code> wenn es wieder verlassen wird.


<source lang="javascript">
<source lang="javascript">
var inhalt = document.querySelector('#inhalt');
document.addEventListener('mousemove', function(event) {
console.log(inhalt);
  console.log('Der Benutzer hat die Maus bewegt');
});
 
document.addEventListener('mouseover', function(event) {
  console.log('Der Benutzer hat die Maus über etwas bewegt');
});
 
document.addEventListener('mouseout', function(event) {
  console.log('Der Benutzer hat die Maus von etwas weg bewegt');
});
</source>
</source>


Klassen-Selektoren werden durch einen vorangestellten Punkt gekennzeichnet.


===keydown, keyup, keypress===
 
Bei Tastatureingaben durch Benutzer auf einem Element werden die Ereignisse <code>keydown</code>, <code>keyup</code> und <code>keypress</code> ausgelöst.
Die drei unterschiedlichen Ereignisse beschreiben die unterschiedlichen Phasen einer Tastatureingabe.
 
<source lang="javascript">
<source lang="javascript">
var kommentare = document.querySelectorAll('.kommentar');
document.addEventListener('keydown', function(event) {
console.log(kommentare);
  console.log('Der Benutzer hat eine Taste gedrückt');
  console.log(event);
});
 
document.addEventListener('keypress', function(event) {
  console.log('Der Benutzer hat eine Taste betätigt');
  console.log(event);
});
 
document.addEventListener('keyup', function(event) {
  console.log('Der Benutzer hat eine Taste losgelassen');
  console.log(event);
});
</source>
</source>


Mithilfe von eckigen Klammern werden beliebige Attribute als Kriterium definiert.


==Wiederverwendung==
Mithilfe von DOM Events kann auch ein genereller Event-Mechanismus für JavaScript implementiert werden.
Dazu wird ein DOM-Element erzeugt, welches als virtuelles Ereignisziel dient und nicht ins Dokument eingehängt wird.


<source lang="javascript">
<source lang="javascript">
var textFelder = document.querySelectorAll('[type="text"]');
var EventDispatcher = function() {
console.log(textFelder);
 
  var target = document.createElement('div');
 
  this.addEventListener = function(type, callback) {
    target.addEventListener(type, callback);
  };
 
  this.dispatchEvent = function(type, detail) {
    var event = new CustomEvent(type, {
      bubbles: true,
      canceable: true,
      detail: detail
    });
    target.dispatchEvent(event);
  };
 
  this.removeEventListener = function(type, callback) {
    target.removeEventListener(type, callback);
  };
 
};
</source>
</source>


Wird kein Attributwert vergeben, wird lediglich auf die Existenz des Attributs geprüft.
 
Diese Klasse kann dann in beliebigen anderen Komponenten dazu genutzt werden um Ereignisse zu erzeugen und zu propagieren.


<source lang="javascript">
<source lang="javascript">
var versteckteElemente = document.querySelectorAll('[hidden]');
var Spieler = function(name) {
console.log(versteckteElemente);
  this.eventDispatcher = new EventDispatcher();
 
  this.springe = function() {
    this.eventDispatcher.dispatchEvent('springen', this);
  }
 
  this.wennSpielerSpringt = function(callback) {
    this.eventDispatcher.addEventListener('springen', callback);
  }
}
var spieler = new Spieler('John');
spieler.wennSpielerSpringt(function() {
  console.log('Spieler springt');
});
spieler.springe();
</source>
</source>


Die oben genannten Kriterien können beliebig miteinander kombiniert werden.
=Timer=
 
 
==JavaScript-Thread==
 
 
Der Browser stellt für jede Web-Seite einen einzelnen Thread zur Verfügung.
Dieser kümmert sich um das Rendering, die Event-Verarbeitung und die Ausführung von JavaScript.
Mit Ausnahme des HTML5-Standards der WebWorker gibt es in JavaScript kein Multithreading.
 
Es ist demnach wichtig, diesen Thread durch Ausführung von Code nicht zu lange zu blockieren.
 
 
Intern bestitzt der Thread eine Liste an Aufgaben die als nächstes ausgeführt werden, der sogenannten Queue.
Wird eine Web-Seite zum ersten Mal geladen führt dies gleich zu mehreren Einträge in der Queue, wie zum Beispiel:
 
* Parsen des Markups und Aufbau des DOMs
* Parsen des CSS und Aufbau des CSSOMs
* Rendering der Seite
* Ausführung von geladenem JavaScript-Code
 
 
Manche Aufgaben benötigen unter Umständen viel Zeit und können die Ausführung anderer Aufgaben beeinflussen.
Nimmt ein Stück Code die CPU zu lange in Anspruch kann der Browser z.B. keine Benutzereingaben mehr vearbeiten.
Um dies zu vermeiden ist im Browser ein Code-Limit für JavaScript eingebaut, bei dem der Browser eine Warnung ausgibt.


<source lang="javascript">
<source lang="javascript">
var wichtigeKommentare = document.querySelectorAll('.kommentar.wichtig');
var programmStart = new Date();
console.log(wichtigeKommentare);
var wartezeitInMs = 5000;
var versteckteAbsaetze = document.querySelectorAll('p[hidden]');
while (new Date() - programmStart < wartezeitInMs) {
console.log(textFelder);
  // nichts tun
};
</source>
</source>


Es ist möglich Kriterien für übergeordnete Elemente zu definieren indem man die Selektoren durch ein Leerzeichen trennt.


==Timeouts==
Mit <code>setTimeout(callback, delayMs)</code> kann ein einmalig auszuführenden Timer auf der Thread-Queue angemeldet werden.
Die übergebene Callback-Funktion wird '''ungefähr''' nach einer Wartezeit mit Länge der übergebenen Dauer ausgeführt.


<source lang="javascript">
<source lang="javascript">
var versteckteElementeInnerhalbVonKommentaren = document.querySelectorAll('.kommentar [hidden]');
var programmStart = new Date();
var loggeNachricht = function() {
  var vergangeneZeit = new Date() - programmStart;
  console.log(vergangeneZeit + 'ms nach Programmstart');
};
setTimeout(loggeNachricht, 500);
</source>
</source>


'''Anmerkung:''' Die Komplexität einer CSS-Selektorgruppe beeinflusst die Performance einer Suche.


Aufgrund der vielen Aufgaben des Threads kann die Wartezeit überschritten werden, wenn andere Aufgaben viel Zeit beanspruchen.
<source lang="javascript">
var programmStart = new Date();
var loggeNachricht = function() {
  var vergangeneZeit = new Date() - programmStart;
  console.log(vergangeneZeit + 'ms nach Programmstart');
};
setTimeout(loggeNachricht, 500);
while (new Date() - programmStart < 1000) {
  // nichts tun
}
</source>
Eine Wartezeit von 0 Millisekunden heißt, dass der Callback so bald wie möglich ausgeführt werden soll.
Abhängig von der Browser-Implementierung kann es sein, dass die Wartezeit aber mindestens 15 Millisekunden beträgt.
<source lang="javascript">
var programmStart = new Date();
var loggeNachricht = function() {
  var vergangeneZeit = new Date() - programmStart;
  console.log(vergangeneZeit + 'ms nach Programmstart');
};
setTimeout(loggeNachricht, 0);
</source>


==Hinzufügen von Elementen==


Neu erstellte Elemente können an beliebigen Stellen im Dokument als untergeordneter Knoten eingefügt werden.
Es ist möglich mit Timern CPU-intensive Aufgaben in mehrere Teile zu spalten oder auch Aufgaben wiederholt auszuführen.
Dazu erzeugt man einfach eine Funktion welche sich wiederum selbst durch einen <code>setTimeout()</code>-Aufruf als Timer registriert.


<source lang="javascript">
<source lang="javascript">
var absatz = document.createElement('p');
var loggeAktuelleZeit = function() {
document.documentElement.appendChild(p);
  console.log(new Date());
  setTimeout(loggeAktuelleZeit, 15);
};
loggeAktuelleZeit();
</source>
</source>


Demzufolge kann erst nach einem Element gesucht werden um ihm dann ein neues Element hinzuzufügen.
 
Durch den Aufruf von <code>clearTimeout(timeoutId)</code> kann ein noch nicht ausgeführter Timer abgemeldet werden.
Dabei wird der Eintrag mit gegebener Timeout-ID aus der Thread-Queue gelöscht.
Die Timeout-ID wird durch <code>setTimeout()</code> als numerischer Rückgabewert bereitgestellt.


<source lang="javascript">
<source lang="javascript">
var body = document.querySelector('body');
var timeoutId = null;
var absatz = document.createElement('p');
var loggeAktuelleZeit = function() {
body.appendChild(absatz);
  console.log(new Date());
  timeoutId = setTimeout(loggeAktuelleZeit, 15);
};
loggeAktuelleZeit();
setTimeout(function() {
  clearTimeout(timeoutId);
}, 2500);
</source>
</source>




==Modifizieren von Elementen==
'''Anmerkung:''' <code>setTimeout()</code> eignet sich vor allem für wiederholende Aufgaben welche nichts mit Rendering zu tun haben.
 


Mithilfe der entsprechenden DOM-Schnittstellen können Text, Attribute sowie das Styling von Elementen beeinflusst werden.
==Intervalle==


===Ändern von Text===


Eine simple aber auch mächtige Weise um den Inhalt eines Elements zu beeinflussen stellt die Eigenschaft <code>innerHTML</code> dar.
Für wiederholt auszuführenden Timer existiert die Funktion <code>setInterval(callback, delayMs)</code>.
Als Argumente werden ein Callback und eine Wartezeit zwischen den einzelnen Ausführungen des Callbacks akzeptiert.


<source lang="javascript">
<source lang="javascript">
var body = document.querySelector('body');
var loggeAktuelleZeit = function() {
body.innerHTML = 'Hallo Welt';
  console.log(new Date());
};
setInterval(loggeAktuelleZeit, 15);
</source>
 
 
Analog zu <code>setTimeout()</code> wird eine ID zurückgegeben, welche für die Abmeldung durch <code>clearInterval()</code> genutzt werden kann.
 
<source lang="javascript">
var loggeAktuelleZeit = function() {
  console.log(new Date());
};
var intervalId = setInterval(loggeAktuelleZeit, 15);
setTimeout(function() {
  clearInterval(intervalId);
}, 2500);
</source>
 
 
<code>setInterval()</code> wirkt komfortabler verglichen mit <code>setTimeout()</code>.
Der Nachteil ist, dass die Messung der Wartezeit vor der Callback-Ausführung beginnt.
Dauert eine Aufgabe zu lange wird der Thread sehr stark beansprucht.
 
<source lang="javascript">
var warteEineBestimmeZeit = function() {
  var funktionsStart = new Date();
  var wartezeitInMs = 50;
  while (new Date() - funktionsStart < wartezeitInMs) {
    // nichts tun
  };
};
setInterval(warteEineBestimmeZeit, 15);
</source>
</source>


Neben einfachen Text-Änderungen lässt sich mithilfe dieser Eigenschaft das gesamte enthaltene HTML beeinflussen.
Obwohl der CPU noch Zeit zur Verfügung steht, ist sie fast nur damit beschäftigt überfällige Callbacks auszuführen.


Anstatt also mithilfe der DOM-Schnittstelle einzelne Elemente zu erzeugen kann einfach Markup als Wert zugewiesen werden.
 
'''Tipp:''' <code>setInterval()</code> eignet sich für wiederholende Aufgaben deren Dauer nicht exakt vorhersehbar ist.
 
 
Aufgrund der verschiedenen Aufgaben des JavaScript-Threads ist eine exakte Wartezeit nicht garantiert.
Sollen sich Werte exakt zeitabhängig verändern können Datum-Objekte in die Berechnungen mit einbezogen werden.


<source lang="javascript">
<source lang="javascript">
var body = document.querySelector('body');
var spieler = {position: 0, geschwindigkeitProSekunde: 1};
body.innerHTML = '<p>Hallo <strong>Welt!</strong></p>';
var letzterTimerAufruf = new Date();
var bewegeSpieler = function() {
  var vergangeneZeitInMs = new Date() - letzterTimerAufruf;
  spieler.position += geschwindigkeitProSekunde * (vergangeneZeitInMs / 1000);
  console.log('Aktuelle Position: ' + spieler.position);
  letzterTimerAufruf = new Date();
  setTimeout(bewegeSpieler, 1000);
};
bewegeSpieler();
</source>
</source>


===Ändern von Attributen===


Für den Zugriff auf Attribute eines HTML-Elements gibt es die Funktionen <code>getAttribute()</code> und <code>setAttribute()</code>.
==Animation Frames==
 
 
Mithilfe von <code>setTimeout()</code> ist es möglich Animationen zu implementieren.
Dazu werden im Callback die gewünschten Elemente in ihrer Form und Position verändert.


<source lang="javascript">
<source lang="javascript">
var p = document.createElement('p');
var spielerElement = document.createElement('div');
p.innerHTML = 'Tolle Website!';
spielerElement.setAttribute('style', 'width: 40px; height: 40px; ' +
p.setAttribute('class', 'kommentar');
  'background-color: #F00; position: absolute; top: 0px');
document.querySelector('body').appendChild(p);
document.querySelector('body').appendChild(spielerElement);
var xPosition = 0;
var bewegeSpieler = function() {
  xPosition = xPosition + 1;
  spielerElement.style.left = xPosition + 'px';
  setTimeout(bewegeSpieler, 15);
};
bewegeSpieler();
</source>
 
Hier wird versucht das Rechteck ca. 60 Mal pro Sekunde zu bewegen.
Es kann aber vorkommen, dass der Browser zu stark beansprucht ist und diese Frequenz nicht eingehalten wird.
 
 
Deshalb gibt es für Animationen und visuelle Änderungen die Funktion <code>requestAnimationFrame(callback)</code>.
Diese erwartet nur einen Callback und kümmert sich intern darum wann der Callback das nächste Mal ausgeführt wird.


console.log(p.getAttribute('class'));
<source lang="javascript">
var spielerElement = document.createElement('div');
spielerElement.setAttribute('style', 'width: 40px; height: 40px; ' +
  'background-color: #F00; position: absolute; top: 0px');
document.querySelector('body').appendChild(spielerElement);
var xPosition = 0;
var frameId = null;
var bewegeSpieler = function() {
  xPosition = xPosition + 1;
  spielerElement.style.left = xPosition + 'px';
  frameId = requestAnimationFrame(bewegeSpieler);
};
bewegeSpieler();
</source>
</source>
Analog zu <code>clearTimeout()</code> und <code>clearInterval()</code> gibt es die Funktion <code>cancelAnimationFrame(frameId)</code> für die Abmeldung.
Diese akzeptiert eine Frame-ID welche wiederum von <code>requestAnimationFrame()</code> zurückgegeben wird.
'''Tipp:''' Es ist empfehlenswert Animationen und visuelle Änderungen mit <code>requestAnimationFrame()</code> zu implementieren.

Aktuelle Version vom 13. Juni 2019, 10:40 Uhr

TO BE DONE

Ziel

Ziel dieses Tutoriums ist es die Grundlagen und Besonderheiten bei der JavaScript-Entwicklung im Browser zu vermitteln. Zur Veranschaulichung sind viele Code-Beispiele gegeben.


Einführung

Native vs. Host

In einer JavaScript-Umgebung existieren zwei Arten von Objekte. Native Objekte werden von der Engine bereitgestellt und sind durch den ECMAScript-Standard abgedeckt. Host-Objekte sind nicht immer standardisiert und werden von der Umgebung bereitgestellt. Im Browser handelt es sich bei Host-Objekten um Schnittstellen für den Zugriff auf den Browser und externe Geräte.


HTML5-Standard

Obwohl es den ECMAScript-Standard gibt, muss man davon ausgehen, dass sich die verfügbaren APIs von Browser zu Browser unterscheiden können. Durch die steigende Entwicklungsgeschwindigkeit der Browser-Hersteller und den HTML5-Standard verbessert sich diese Situation.

Heutzutage kann man davon ausgehen, dass jeweils die neuesten Versionen von Firefox und Chrome die DOM-API und viele der neu verfassten HTML5-APIs bereits standardkonform unterstützen.


Inline Code vs. externe Dateien

Eine im Browser angezeigte Web-Seite besteht meist aus HTML, CSS und JavaScript, wobei nur HTML zwingend verlangt ist. Der JavaScript-Code kümmert sich normalerweise um die dynamischen Aspekte, wie Interaktionen und das Nachladen von Inhalten.


Der Browser lädt ein HTML-Dokument unter der angegebenen URL herunter und evaluiert dieses. Das Dokument enthält optional Verweise auf CSS-Dateien, Bilder, JavaScript-Dateien und andere Medien. Neben Verweisen auf externen JavaScript-Code kann das Dokument auch Inline-Code enthalten. Ob es sich um externe Dateien oder Inline-Code handelt wirkt sich auf die Ladezeiten aus, der Code selbst wird davon nicht beeinflusst.


Anmerkung: Die Reihenfolge der eingebunden Code-Blöcke und angegebenen Dateiverweise ist wichtig, da diese ebenso die Reihenfolge der Code-Ausführung darstellt.


JavaScript-Kontext

Für jede Web-Seite, unabhängig davon ob sie im separaten Fenster oder als Tab angezeigt wird, erzeugt der Browser einen separaten isolierten JavaScript-Kontext, in dem Code ausgeführt wird. Das bedeutet, dass alle Objekte und Funktionen nur so lange existieren bis die Seite neu geladen oder verlassen wird. Ein Zugriff auf andere Seiten ist aus Sicherheitsgründen nicht erlaubt.

Window

Das globale Objekt window referenziert alle im Browser verfügbaren Objekte und Werte als Eigenschaften (native und host).

console.log(window.Object);
console.log(window.XMLHttpRequest);

Für den Zugriff auf globale Eigenschaften ist es nicht notwendig explizit window zu schreiben.

console.log(Date == window.Date);
console.log(new Date());

Alle nicht innerhalb von Funktionen deklarierten Variablen werden automatisch an das globale Objekt angehängt.

var globaleVariable = 'global';
var globaleFunktion = function() {};
console.log(window.globaleVariable);
console.log(window.globaleFunktion);

Modifikation von Eigenschaften

Da es sich um ein normales Objekt handelt können die meisten Eigenschaften modifiziert und auch gelöscht werden.

Anmerkung: Veränderungen von globalen Eigenschaften sind nicht sinnvoll und können schwerwiegende Fehler verursachen.

window.Object = {};
var objekt = new Object();

delete window.Array;
var array = new Array();

Das window-Objekt wird vom Browser mithilfe der Konstruktorfunktion Window erzeugt, welche auch global verfügbar ist.

console.log(window.constructor == window.Window);

Popups und Frames

Neben dem globalen Fenster-Objekt kann eine Web-Seite beliebig viele weitere window-Objekte enthalten. Dabei handelt es sich dann entweder um durch Code erzeugte Popups oder aber um Frames (besser bekannt als iframes).

window.open('https://www.google.com', 'Google im Popup', 'width=400,height=300,resizable=yes');

Frames können entweder durch das entsprechende HTML oder aber auch wie im Code gezeigt programmatisch erzeugt werden.

Browser-Schnittstellen

Abgesehen von den nativen Objekten notwendigen Objekte enthält window wichtige Browser- und Geräte-Schinttstellen. Dazu zählen zum Beispiel Funktionen um Dialoge im Browser zu öffnen oder aber sogar um Game-Controller zu verwenden.

var name = window.prompt('Wie ist Ihr Name?');
window.alert('Hello ' + name);
window.alert('Die aktuelle Seite ist ' + window.innerWidth + 'px breit');

console.log(window.Gamepad);

Eine komplette Liste der verfügbaren Eigenschaften finden Sie auf dieser MDN-Seite. In den folgenden Abschnitten werden exemplarisch ein paar wichtige Objekte im Detail beschrieben.

Beispiel für Browser-Schnittstellen

Location

location ist ein globales Objekt welches die aktuelle URL und ihre Bestandteile enthält und diese auch verändern kann. Das Objekt wird analog zum window-Objekt mithilfe der Konstruktorfunktion Location erstellt.

console.log(window.location.constructor == window.Location);

Abfragen der URL

Die Eigenschaft href enhtält die gesamte aktuelle URL. Alternativ kann auch location.toString() genutzt werden.

console.log(location.href);
console.log(location.toString());

Um einzelne Bestandteile der URL ermitteln zu können werden viele separate Eigenschaften bereitgestellt.

console.log(location.protocol);
console.log(location.host);
console.log(location.port);
console.log(location.pathname);
console.log(location.hash);

Verändern der URL

Diese Eigenschaften können nicht nur lesend genutzt werden, sondern auch um Änderungen an der aktuellen URL vorzunehmen.

Mit der Ausname von location.hash führen Änderungen der Eigenschaften zum Verlassen der aktuellen Seite.

location.protocol = 'http';
location.port = '8080';
location.hash = '#anker';
location.href = 'https://www.google.com';

Des Weiteren gibt es ein paar Funktionen um die URL zu modifizieren oder aber die Seite neuzuladen:</source>

location.assign('https://www.google.com');
location.reload(true);

History

Das globale history-Objekt ermöglicht den programmatischen Zugriff auf den Browser-Verlauf. Es werden Funktionen zur Verfügung gestellt um im Verlauf beliebig viele Schritte zurück und vorwärts zu gehen.

history.back();
history.forward();
history.go(-3);
history.go(3);

Anmerkung: Aus Sicherheitsgründen ist es nicht erlaubt auf Informationen einzelner Einträge im Verlauf zuzugreifen.

Navigator

Für detaillierte Informationen über den aktuellen Client bzw. Browser existiert das navigator-Objekt. Die meistgenutzte Eigenschaft navigator.userAgent ermöglicht das Feststellen des Browser-Herstellers.

console.log(navigator.userAgent);
console.log(navigator.battery);


Document Object Model

Einem window-Objekt können beliebig viele Dokumentenobjekte zugeordnet werden, mindestens aber ein Hauptdokument. Das Hauptdokument wird über die globale Variable document zur Verfügung gestellt. Die Schnittstelle für den Dokumentenzugriff wird durch das Document Object Model spezifiziert.


Objektmodell

Ein Document-Objekt repräsentiert ein vom Browser interpretiertes HTML-Dokument als Objektmodell mit einer Baumstruktur. Dabei werden Tags, Texte und Kommentare in Knoten umgewandelt, wobei Tag-Knoten wiederum Unterknoten haben können.

<html>
  <head>
    <title>Titel</title>
  </head>
  <body>
    <p>
      Text
      <!-- Kommentar -->
    </p>
    <input type="text" value="Eingabe">

  </body>
</html>


Das Markup wird in folgendes Objektmodell übersetzt:

  • Ein html-Knoten mit den Unterknoten head und body
  • Ein head-Knoten mit dem Unterknoten title
  • Ein title-Knoten mit einem Text-Unterknoten
  • Ein body-Knoten mit den Unterknoten p und input
  • Ein p-Knoten mit einem Text- und einem Kommentar-Unterknoten
  • Ein input-Knoten

Tipp: Achtet man bei HTML auf eine korrekte Einrückung so ist das resultierende Objektmodell meist gut erkennbar.


Markup-Korrekturen

Bei dem Objektmodell handelt es sich nicht um exaktes Abbild des originalen Markups. Der Unterschied wird klar, wenn man das Resultat von invalidem HTML untersucht.

<html>
  <head>
    <title>Titel</title>
  </head>
  <body>
    <table>
      <tr>
        <td>Tabelle</td>
      </tr>
    </table>
  </body>
</html>

Der Browser fügt zwischen dem table- und dem tr-Knoten einen tbody-Knoten ein.


Aufbau von Dokumenten

Node-Objekte

Jeder Knoten innerhalb eines Dokumentenobjekts ist direkt oder indirekt abgeleitet vom Node-Konstruktor/-Prototypen. Mit Ausnahme des Dokumentenobjekts selbst wird allen Knoten ein Dokument zugeordnet durch die ownerDocument-Eigenschaft.


Des Weiteren hat jeder Knoten einen numerischen Typen, der in der Eigenschaft nodeType gespeichert wird. Die gängisten Typen von Dokumentenknoten sind:

  • 1: ELEMENT_NODE
  • 3: TEXT_NODE
  • 8: COMMENT_NODE
  • 9: DOCUMENT_NODE

Eine vollständige Referenz der Eigenschaften von Node-Objekten finden Sie auf dieser MDN-Seite.


Document-Objekte

Das Dokument selbst repräsentiert ein abstraktes Dokument und somit kein HTML-Element. Es verweist aber auf das html-Element mit der Eigenschaft documentElement.

console.log(document.documentElement);

Mithilfe eines Dokuments ist es möglich neue Element-Objekte zu erstellen, einzufügen und diese zu suchen.


Element-Objekte

Jedes HTML-Tag wird in einen Knoten übersetzt, welcher mit dem Element-Konstruktor erzeugt wird. Element-Objekte besitzen Schnittstellen um auf Eigenschaften des Tags zuzugreifen und diese zu modifizieren.

console.log(document.documentElement.tagName);
console.log(document.documentElement.innerHTML);

Ebenso wie Dokumente können Element-Objekten weitere Node-Objekte als Unterknoten angehängt und nach ihnen gesucht werden.


Erstellen von Elementen

Neue Element-Objekte werden über die Funktion createElement() eines document-Objekts unter Angabe des Tag-Namens erzeugt.

var absatz = document.createElement('p');
console.log(absatz.tagName);
console.log(absatz.nodeType);


Bei der Erstellung wird das neue Element dem augerufenen Dokument durch die Eigenschaft ownerDocument als zugehörig markiert.

var block = document.createElement('div');
console.log(block.ownerDocument);


Da die Schnittstelle eines Dokuments nicht mit HTML-Standards gekoppelt ist, können beliebige Tag-Namen vergeben werden.

var meinElement = document.createElement('mein-element');
console.log(meinElement);


Suchen von Elementen

Besonders wichtig bei der Arbeit mit Dokumenten ist die Suche von einzelnen oder mehreren Elementen. Aufgrund dessen haben sich die Schnittstellen für die Suche im Browser über die letzten Jahre rapide verbessert.


Vorgefertige Kriterien

Die ursprünglichen Funktionen zum Suchen von Elementen erlauben wenige festgelegte Kriterien.

var spielfeld = document.getElementById('spielfeld');
console.log(spielfeld);

var divElemente = document.getElementsByTagName('div');
console.log(divElemente);

var gegnerElemente = document.getElementsByClassName('gegner');
console.log(gegnerElemente.length);

Auf diese Weise können Elemente anhand der ID, Klasse oder des Tag-Namen gesucht werden. Der Rückgabewert ist bei der ID-Suche ein Node-Objekt und bei den anderen eine NodeList, ein Array-artiges Objekt von Nodes.


CSS-Selektoren

Die Suche mithilfe von vorgefertigen Kriterien hat deutliche Limitierungen. Deswegen enstand durch Bibliotheken wie jQuery das Konzept von Query-Selektoren, welche als CSS-Selektoren standardisiert werden und in vielen Browser verfügbar sind.


Eine CSS-Selektorgruppe beschreibt beliebig viele Kriterien, die auf ein oder mehrere HTML-Elemente zutreffen. Der Name wurde so gewählt, da diese ebenso in Stylesheets genutzt werden um Elemente auszuwählen. Aufgrund der Kombinierbarkeit von CSS-Selektoren ergibt sich eine sehr mächtige Suchfunktionalität.


Es gibt zwei Funktionen für die Suche mit CSS-Selektoren:

  • document.querySelector() liefert das erste Element zurück welches die Kriterien erfüllt
  • document.querySelectorAll() liefert alle Elemente zurück welche die Kriterien erfüllen in Form einer Node-Liste


Mögliche Kriterien

Das einfachste Kriterium für einen CSS-Selektor ist die Angabe eines Tag-Namen.

var absaetze = document.querySelectorAll('p');
console.log(absaetze);


Die Angabe einer ID eines HTML-Elements erfolgt durch eine vorangestellte Raute (#).

var spielfeldElement = document.querySelector('#spielfeld');
console.log(spielfeldElement);


Klassen-Selektoren werden durch einen vorangestellten Punkt (.) gekennzeichnet.

var gegnerElemente = document.querySelectorAll('.gegner');
console.log(gegnerElemente);


Die Mächtigkeit von CSS-Selektoren ergibt sich vor allem durch die beliebige Kombinationsmöglichkeiten von Selektoren.

var schwierigeGegnerElemente =
  document.querySelectorAll('.gegner.schwierig');
console.log(schwierigeGegnerElemente);


Anmerkung 1: Die Komplexität einer CSS-Selektorgruppe beeinflusst die Performance einer Suche.


Anmerkung 2: Eine vollständige Erklärung der Funktionsweise von CSS-Selektoren ist nicht Ziel dieses Tutoriums. Diese Einführung dient lediglich dazu ein grundsätzliches Verständnis für den Umgang mit Selektoren zu vermitteln.


Hinzufügen von Elementen

Neu erstellte Elemente können im Dokument als untergeordnete Knoten mithilfe von appendChild() eingefügt werden.

var spielerElement = document.createElement('div');
document.documentElement.appendChild(spielerElement);


Der künftig übergeordnete Knoten muss angesprochen werden, wenn das Element an einer bestimmten Stelle landen soll.

var spielerElement = document.querySelector('div');
var spielfeld = document.createElement('div');
spielfeld.appendChild(spielerElement);


Modifizieren von Elementen

Mithilfe der entsprechenden DOM-Schnittstellen können Text, HTML, Attribute sowie das Styling von Elementen beeinflusst werden.


Ändern von Text und HTML

Der gesamte HTML-Inhalt eines Elements kann mithilfe der Eigenschaft innerHTML beeinflusst werden.

var body = document.querySelector('body');
body.innerHTML = 'Hallo Welt';


Abseits von einfachen Text-Änderungen lässt sich sogar das gesamte enthaltene HTML beeinflussen. Anstatt also mit der DOM-API einzelne Elemente zu erzeugen kann einfach Markup als Wert zugewiesen werden.

var body = document.querySelector('body');
body.innerHTML = '<p>Hallo <strong>Welt!</strong></p>';


Ändern von Attributen

Für den Zugriff auf die Attribute eines HTML-Elements gibt es die Funktionen getAttribute() und setAttribute(). Beide akzeptieren als erstes Argument den Namen des Attributs, der Setter akzeptiert außerdem einen zu setzenden Wert.

var spielerElement = document.createElement('div');
spielerElement.innerHTML = '_|_';
spielerElement.setAttribute('class', 'spieler');
document.querySelector('body').appendChild(spielerElement);

console.log(spielerElement.getAttribute('class'));


Anmerkung: Besonders häufige Attributsänderungen sind das Hinzufügen und/oder Entfernen von Klassen. Dafür gibt es in neueren Browsern die Liste classList auf jedem Element-Objekt, welche die Arbeit erleichtert.

var spielerElement = document.querySelector('.spieler');
spielerElement.classList.add('unbesiegbar');


Ändern von CSS-Eigenschaften

Neben dem Document Object Model existiert außerdem der CSS Object Model Standard welcher die Schnittstelle für den Zugriff auf CSS-Eigenschaften von DOM-Objekten standardisiert.


Ein jedes DOM-Element vom Typ Element besitzt eine Eigenschaft style welche alle CSS-Eigenschaften beherbergt.

var body = document.querySelector('body');
console.log(body.style.color);
console.log(body.style.padding);


Diese Eigenschaften spiegeln die in Stylesheets definierten Regeln wieder, jedoch inklusive dem kaskadierenden Charakter. Das heisst dass ein Element-Objekt nicht zwingend selbst eine color besitzt, diese jedoch für die Anzeige vererbt bekommt.


Die Eigenschaften können ebenso beliebig modifiziert werden, was zu einer sofortigen Anpassung der Darstellung führt.

var body = document.querySelector('body');
body.style.backgroundColor = '#AAF';


Löschen von Elementen

Ebenso wie neue Elemente einem Dokument hinzugefügt werden können, so können diese oder bestehende auch gelöscht werden. Dazu muss man den übergeordneten Knoten ansteuern und mithilfe von removeChild() die Entfernung durchführen.

var spielfeld = document.querySelector('.spielfeld');
var spielerElement = spielfeld.querySelector('.spieler');
spielfeld.removeChild(spielerElement);


Ist einem der übergeordnete Knoten nicht bekannt so kann diesen mithilfe der Knotenverweise ermitteln.

var entferneKnoten = function(knoten) {
  if (!knoten.parentNode) {
    throw new Error('no parent node');
  }
  knoten.parentNode.removeChild(knoten);
}

var spielerElement = document.querySelector('.spieler');
entferneKnoten(spielerElement);

DOM Events

Ereignisorientierte Programmierung

Für die Verarbeitung von stets verfügbaren Informationen eignet sich ein sequentieller Ablauf von Befehlen. Ergeben sich Informationen jedoch dynamisch zur Laufzeit kann die ereignisorientierte Programmierung genutzt werden.

Hierbei wird nicht aktiv auf eine Information gewartet, das Programm wird benachrichtigt wenn ein Ereignis eintritt. Somit gibt es nicht nur einen Kontrollfluss, sondern beliebig viele, welche sogar parallel ablaufen können.


Ereignisse im Browser

Bei der Interaktion mit Websites werden viele Ereignisse ausgelöst. Zuerst wird die HTML-Seite geladen und gerendert, danach geschieht dies für alle referenzierten Inhalte. Diese Vorgänge lösen Ereignisse aus, welche durch den Browser selbst verursacht werden.

Zum Beispiel gibt es Ereignisse für Medien, welche beschreiben, dass diese fertig geladen wurden.


Sobald der Browser beginnt eine Web-Seite zu rendern können Ereignisse durch den Benutzer ausgelöst werden. Fast jede Interaktion löst ein Ereignis aus, welches wiederum in bestimmten Reaktionen resultiert.

Beispiele: Klickt der Benutzer einen Link so ist das ein Ereignis, welches zur Folge hat dass die eine URL geladen wird. Wird ein Scroll-Balken bewegt ergibt sich daraus ein Ereignis, welches den Browser scrollen lässt.


Zu den wichtigsten Arten von durch Benutzer ausgelöste Ereignissen zählen: Mausbewegungen und -klicks, Tastatureingaben und Scrolling.

Eine vollständige Auflistung von Ereignissen finden Sie auf dieser MDN-Seite. Nebem dem Benutzer gibt es noch andere Auslöser wie zum Beispiel verbundene Server.


Aufbau eines Ereignisses

Ein im Browser auftretendes Ereignis besitzt immer drei folgende Merkmale:

  • Ereignis-Name: eine Zeichenkette die das Ereignis in der Gegenwart beschreibt, z.B. "click" oder "mousemove"
  • Ereignis-Daten: ein Datenobjekt mit einer pro Ereignis festgelegten Struktur
  • Ereignis-Ziel: ein Objekt das als Ziel des Ereignis gilt; in der Regel handelt es sich um DOM-Elemente

Beispiel: Eine Tastatureingabe in einem Formularfeld, bei dem das Zeichen @ eingegeben wird.


Phasen der Ereignispropagierung

Handelt es sich bei dem ursprünglichen Ziel eines Ereignisses um ein DOM-Objekt werden in der Regel drei Phasen durchlaufen.


Capturing-Phase

In Phase 1, der Capturing-Phase, wird ein Ereignis mit dem obersten Dokumentenknoten als Ziel ausgelöst. Dabei handelt es sich in der Regel um das document-Objekt, welches alle andere Nodes als Unterknoten enthält. Es werden dann schrittweise Ereignisse ausgelöst für jeden Unterknoten auf dem Pfad zum ursprünglichen Ereignisziel.

</source> + window - Ereignis mit window als aktuelles Ziel (optional)

 + document  - Ereignis mit body als aktuelles Ziel
   + body    - Ereignis mit body als aktuelles Ziel
     + p     - ursprüngliches Ziel

</source>

Anmerkung: Bei manchen Ereignissen ist window das oberste Objekt in der Hierarchie.


Target-Phase

Ist der Browser beim ursprünglichen Ziel angelangt wird in Phase 2 gewechselt, der sogenannten Target-Phase. Dabei wird ein einzelnes Ereignis ausgelöst, für welches das ursprüngliche DOM-Objekt als Ziel verwendet wird.


Bubbling-Phase

Zum Abschluss der Ereignispropagierung wechselt der Browser in Phase 3, der sogenannten Bubbling-Phase. Hierbei wird der Pfad der Capturing-Phase in umgekehrter Richtung durchlaufen und jeweils wieder ein Ereignis ausgelöst.

Anmerkung: In Bibliotheken wie jQuery hat man nur Zugriff auf die Bubbling-Phase, nicht auf die Capturing-Phase.


Browser-Unterschiede

Capturing und Bubbling bilden die Tatsache ab, dass wenn ein Ereignis auf einem Element stattfindet, dieses Ereignis ebenso für die übergeordneten Elemente gültig ist. Beispiel: Wird ein Mensch in einem Krankenhaus geboren, so wird er auch in einer Stadt und in einem Land geboren.

Einer der beiden Phasen wäre ausreichend für die Ereignisvearbeitung. Aufgrund von unterschiedlichen Browser-Implementierungen existieren heutzutage jedoch beide gleichzeitig.


Globale Ereignisse

Beschreibt ein Ereignis ein globales Geschehen wird oftmals das document oder window-Objekt als Ereignisziel verwendet. In diesen Fällen ist es möglich dass weder Capturing- noch eine Bubbling-Phase durchlaufen werden.

Beispiel: Das Ergeinis DOMContentLoaded beschreibt, dass eine Seite (ohne seine externen Medien) fertig geladen wurde.


Ereignisverarbeitung

Mithilfe der DOM-Schnittstelle erhält man die Möglichkeit Ereignisse selbst zu verarbeiten und darauf zu reagieren.


Anmeldung

Die Funktion addEventListener kann auf jedem DOM-Objekt, dem document-Objekt und dem window-Objekt aufgerufen werden. Dabei ist das Objekt auf dem die Funktion aufgerufen das Ziel des Ereignisses, welches verarbeiten werden soll.


Die Funktion akzeptiert die folgenden drei Argumente:

  • den Namen des Ereignisses das man verarbeiten möchte in Form eines Strings
  • die Callback-Funktion welche das Ereignis-Objekt erwartet und den Code zur Verarbeitung enthält
  • ein optionales Flag das bestimmt ob das Ereignis in der Capturing- oder Bubbling-Phase verarbeitet werden soll

Standardmäßig ist das Flag false, was bedeutet dass das Ereignis in der Bubbling-Phase verarbeitet wird.


Es wird für ein Ereignis auf einem Ziel ein Event-Lister in Form einer Funktion angemeldet. Beim Eintreten des Ereignisses wird der sogenannte Callback aufgerufen und das entsprechende Ereignis-Objekt übergeben.

var seiteWurdeGeladen = function(event) {
  console.log('Die Seite ist nun fertig geladen');
  console.log(event);
}
document.addEventListener('DOMContentLoaded', seiteWurdeGeladen);
var body = document.querySelector('body');
body.innerHTML = '<a id="link" href="#">Link</a>';
var link = document.querySelector('#link');
var linkWurdeGeklickt = function(event) {
  console.log('Der Link wurde geklickt');
};
link.addEventListener('click', linkWurdeGeklickt);


Aufgrund der Phasen der Ereignispropagierung kann man Ereignisse global verarbeiten, unabhängig davon wo sie stattgefunden haben.

var body = document.querySelector('body');
body.innerHTML = '<a href="#">Link 1</a><br><a href="#">Link 2</a>';
var irgendwoWurdeGeklickt = function(event) {
  console.log('Benutzer hat irgendwo auf der Seite geklickt');
};
document.addEventListener('click', irgendwoWurdeGeklickt);


Callbacks

Ein Callback ist eine Rückruffunktion, welche an ein andere Funktion übergeben wird und zu späteren Zeitpunkten ggf. ausgeführt wird.

var mausWurdeBewegt = function(event) {
  console.log('Benutzer hat die Maus bewegt');
};
document.addEventListener('mousemove', mausWurdeBewegt);


Es ist jedoch nicht garantiert dass eine übergebene Callback-Funktion überhaupt jemals ausgeführt wird.

var esIstOktober = function(event) {
  console.log('Es ist Oktober');
};

var istEsOktober = function(callback) {
  if (new Date().getMonth() == 9) callback();
};

istEsOktober(esIstOktober);


Ereignis-Objekte

Jedem Event-Listener-Callback wird beim Aufruf ein für ein Ereignis spezifisches Ereignis-Objekt übergeben.

document.addEventListener('click', function(event) {
  console.log(event);
  console.log(event.screenX);
  console.log(event.screenY);
  console.log(event.button);
});


Dieses Objekt enthält neben spezifischen auch generelle Ereignisinformationen wie zum Beispiel das Ereignis-Ziel.

document.addEventListener('click', function(event) {
  console.log(event.target);
});


Hinsichtlich der drei Event-Phasen unterscheidet man zwischen ursprünglichem und aktuellem Ziel. Das aktuelle Ziel ist das für welches das Ereignis aktuell gilt und das ursprüngliche das wo es stattgefunden hat.

document.addEventListener('click', function(event) {
  console.log(event.target); // ursprüngliches Ziel
  console.log(event.currentTarget); // aktuelles Ziel
});

Das aktuelle Ziel currentTarget entspricht also immer dem Objekt auf dem man den Event-Handler angemeldet hat.


Abmeldung

Mithilfe der Funktion removeEventListener kann ein angemeldeter Event-Listener wieder entfernt werden. Diese Funktion nur zwei Argumente: Den Ereignis-Namen und die Event-Listener-Funktion.

var mausWurdeBewegt = function(event) {
  console.log(event);
};

document.addEventListener('mousemove', mausWurdeBewegt);
document.removeEventListener('mousemove', mausWurdeBewegt);


Standardverhalten

Nachdem alle Ereignisse verarbeitet wurden führt der Browser gegegebenfalls eine "Default Action" aus. Eine solche Aktion ist das Standardverhalten des Browsers als Reaktion auf das eingetretene Ereignis.

Beispiele:

  • Benutzer klickt auf einen Link -> der Browser lädt die URL
  • Benutzer führt Tastatureingaben in einem Feld aus -> der Browser fügt die Zeichen dem Feld hinzu


Innerhalb einer Event-Listener-Funktion ist es möglich mithilfe von preventDefault() das Standardverhalten zu unterdrücken.

    document.addEventListener('click', function(event) {
      event.preventDefault();
    });

Der oben dargestellte Code führt dazu, dass kein Klick auf einen Link die enthaltene URL aufruft.


Wichtige Events

Die folgenden Ereignisse sind die wichtigsten in der täglichen browser-seitingen Anwendungsentwicklung mit JavaScript.


DOMContentLoaded

Dieses Ereignis beschreibt, dass das HTML geladen und interpretiert wurde und das DOM und CSSOM erzeugt und gerendert wurde. Es wird keine Aussage darüber gemacht ob weitere externe Inhalte bereits geladen wurden, wie z.B. Bilder. Das Ereignis wird ausschliesslich auf dem document-Objekt als Ereignisziel ausgelöst.

document.addEventListener('DOMContentLoaded', function(event) {
  console.log('Mit der Seite kann interagiert werden');
});


load

Dieses Ereignis besagt, dass die gesamte Web-Seite inklusive aller externen referenziereten Inhalte gerendert wurden. Im Gegensatz zu DOMContentLoaded wird dieses Ereignis ausschliesslich auf dem window-Objekt ausgelöst.

window.addEventListener('load', function(event) {
  console.log('Die Seite wurde vollständig geladen');
});


click

Jedes Mal wenn der Benutzer (mit der Maus) auf ein sichtbares DOM-Element klickt wird dieses Ereignis ausgelöst. Bei der Propagierung dieses Ereignisses werden wie alle drei Ereignisphasen (Capturing, Bubbling, Target) durchlaufen.

document.addEventListener('click', function(event) {
  console.log('Der Benutzer hat auf etwas geklickt');
});


mousemove, mouseover, mouseout

Bei Mausbewegungen über DOM-Element wird konstant das Ereignis mousemove mit den entsprechenden Koordinaten ausgelöst. Das Ereignis mouseover wird erzeugt wenn die Maus in ein Element eindringt, mouseout wenn es wieder verlassen wird.

document.addEventListener('mousemove', function(event) {
  console.log('Der Benutzer hat die Maus bewegt');
});

document.addEventListener('mouseover', function(event) {
  console.log('Der Benutzer hat die Maus über etwas bewegt');
});

document.addEventListener('mouseout', function(event) {
  console.log('Der Benutzer hat die Maus von etwas weg bewegt');
});


keydown, keyup, keypress

Bei Tastatureingaben durch Benutzer auf einem Element werden die Ereignisse keydown, keyup und keypress ausgelöst. Die drei unterschiedlichen Ereignisse beschreiben die unterschiedlichen Phasen einer Tastatureingabe.

document.addEventListener('keydown', function(event) {
  console.log('Der Benutzer hat eine Taste gedrückt');
  console.log(event);
});

document.addEventListener('keypress', function(event) {
  console.log('Der Benutzer hat eine Taste betätigt');
  console.log(event);
});

document.addEventListener('keyup', function(event) {
  console.log('Der Benutzer hat eine Taste losgelassen');
  console.log(event);
});


Wiederverwendung

Mithilfe von DOM Events kann auch ein genereller Event-Mechanismus für JavaScript implementiert werden. Dazu wird ein DOM-Element erzeugt, welches als virtuelles Ereignisziel dient und nicht ins Dokument eingehängt wird.

var EventDispatcher = function() {

  var target = document.createElement('div');

  this.addEventListener = function(type, callback) {
    target.addEventListener(type, callback);
  };

  this.dispatchEvent = function(type, detail) {
    var event = new CustomEvent(type, {
      bubbles: true,
      canceable: true,
      detail: detail
    });
    target.dispatchEvent(event);
  };

  this.removeEventListener = function(type, callback) {
    target.removeEventListener(type, callback);
  };

};


Diese Klasse kann dann in beliebigen anderen Komponenten dazu genutzt werden um Ereignisse zu erzeugen und zu propagieren.

var Spieler = function(name) {
  this.eventDispatcher = new EventDispatcher();

  this.springe = function() {
    this.eventDispatcher.dispatchEvent('springen', this);
  }

  this.wennSpielerSpringt = function(callback) {
    this.eventDispatcher.addEventListener('springen', callback);
  }
}
var spieler = new Spieler('John');
spieler.wennSpielerSpringt(function() {
  console.log('Spieler springt');
});
spieler.springe();

Timer

JavaScript-Thread

Der Browser stellt für jede Web-Seite einen einzelnen Thread zur Verfügung. Dieser kümmert sich um das Rendering, die Event-Verarbeitung und die Ausführung von JavaScript. Mit Ausnahme des HTML5-Standards der WebWorker gibt es in JavaScript kein Multithreading.

Es ist demnach wichtig, diesen Thread durch Ausführung von Code nicht zu lange zu blockieren.


Intern bestitzt der Thread eine Liste an Aufgaben die als nächstes ausgeführt werden, der sogenannten Queue. Wird eine Web-Seite zum ersten Mal geladen führt dies gleich zu mehreren Einträge in der Queue, wie zum Beispiel:

  • Parsen des Markups und Aufbau des DOMs
  • Parsen des CSS und Aufbau des CSSOMs
  • Rendering der Seite
  • Ausführung von geladenem JavaScript-Code


Manche Aufgaben benötigen unter Umständen viel Zeit und können die Ausführung anderer Aufgaben beeinflussen. Nimmt ein Stück Code die CPU zu lange in Anspruch kann der Browser z.B. keine Benutzereingaben mehr vearbeiten. Um dies zu vermeiden ist im Browser ein Code-Limit für JavaScript eingebaut, bei dem der Browser eine Warnung ausgibt.

var programmStart = new Date();
var wartezeitInMs = 5000;
while (new Date() - programmStart < wartezeitInMs) {
  // nichts tun
};


Timeouts

Mit setTimeout(callback, delayMs) kann ein einmalig auszuführenden Timer auf der Thread-Queue angemeldet werden. Die übergebene Callback-Funktion wird ungefähr nach einer Wartezeit mit Länge der übergebenen Dauer ausgeführt.

var programmStart = new Date();
var loggeNachricht = function() {
  var vergangeneZeit = new Date() - programmStart;
  console.log(vergangeneZeit + 'ms nach Programmstart');
};
setTimeout(loggeNachricht, 500);


Aufgrund der vielen Aufgaben des Threads kann die Wartezeit überschritten werden, wenn andere Aufgaben viel Zeit beanspruchen.

var programmStart = new Date();
var loggeNachricht = function() {
  var vergangeneZeit = new Date() - programmStart;
  console.log(vergangeneZeit + 'ms nach Programmstart');
};
setTimeout(loggeNachricht, 500);
while (new Date() - programmStart < 1000) {
  // nichts tun
}


Eine Wartezeit von 0 Millisekunden heißt, dass der Callback so bald wie möglich ausgeführt werden soll. Abhängig von der Browser-Implementierung kann es sein, dass die Wartezeit aber mindestens 15 Millisekunden beträgt.

var programmStart = new Date();
var loggeNachricht = function() {
  var vergangeneZeit = new Date() - programmStart;
  console.log(vergangeneZeit + 'ms nach Programmstart');
};
setTimeout(loggeNachricht, 0);


Es ist möglich mit Timern CPU-intensive Aufgaben in mehrere Teile zu spalten oder auch Aufgaben wiederholt auszuführen. Dazu erzeugt man einfach eine Funktion welche sich wiederum selbst durch einen setTimeout()-Aufruf als Timer registriert.

var loggeAktuelleZeit = function() {
  console.log(new Date());
  setTimeout(loggeAktuelleZeit, 15);
};
loggeAktuelleZeit();


Durch den Aufruf von clearTimeout(timeoutId) kann ein noch nicht ausgeführter Timer abgemeldet werden. Dabei wird der Eintrag mit gegebener Timeout-ID aus der Thread-Queue gelöscht. Die Timeout-ID wird durch setTimeout() als numerischer Rückgabewert bereitgestellt.

var timeoutId = null;
var loggeAktuelleZeit = function() {
  console.log(new Date());
  timeoutId = setTimeout(loggeAktuelleZeit, 15);
};
loggeAktuelleZeit();
setTimeout(function() {
  clearTimeout(timeoutId);
}, 2500);


Anmerkung: setTimeout() eignet sich vor allem für wiederholende Aufgaben welche nichts mit Rendering zu tun haben.


Intervalle

Für wiederholt auszuführenden Timer existiert die Funktion setInterval(callback, delayMs). Als Argumente werden ein Callback und eine Wartezeit zwischen den einzelnen Ausführungen des Callbacks akzeptiert.

var loggeAktuelleZeit = function() {
  console.log(new Date());
};
setInterval(loggeAktuelleZeit, 15);


Analog zu setTimeout() wird eine ID zurückgegeben, welche für die Abmeldung durch clearInterval() genutzt werden kann.

var loggeAktuelleZeit = function() {
  console.log(new Date());
};
var intervalId = setInterval(loggeAktuelleZeit, 15);
setTimeout(function() {
  clearInterval(intervalId);
}, 2500);


setInterval() wirkt komfortabler verglichen mit setTimeout(). Der Nachteil ist, dass die Messung der Wartezeit vor der Callback-Ausführung beginnt. Dauert eine Aufgabe zu lange wird der Thread sehr stark beansprucht.

var warteEineBestimmeZeit = function() {
  var funktionsStart = new Date();
  var wartezeitInMs = 50;
  while (new Date() - funktionsStart < wartezeitInMs) {
    // nichts tun
  };
};
setInterval(warteEineBestimmeZeit, 15);

Obwohl der CPU noch Zeit zur Verfügung steht, ist sie fast nur damit beschäftigt überfällige Callbacks auszuführen.


Tipp: setInterval() eignet sich für wiederholende Aufgaben deren Dauer nicht exakt vorhersehbar ist.


Aufgrund der verschiedenen Aufgaben des JavaScript-Threads ist eine exakte Wartezeit nicht garantiert. Sollen sich Werte exakt zeitabhängig verändern können Datum-Objekte in die Berechnungen mit einbezogen werden.

var spieler = {position: 0, geschwindigkeitProSekunde: 1};
var letzterTimerAufruf = new Date();
var bewegeSpieler = function() {
  var vergangeneZeitInMs = new Date() - letzterTimerAufruf;
  spieler.position += geschwindigkeitProSekunde * (vergangeneZeitInMs / 1000);
  console.log('Aktuelle Position: ' + spieler.position);
  letzterTimerAufruf = new Date();
  setTimeout(bewegeSpieler, 1000);
};
bewegeSpieler();


Animation Frames

Mithilfe von setTimeout() ist es möglich Animationen zu implementieren. Dazu werden im Callback die gewünschten Elemente in ihrer Form und Position verändert.

var spielerElement = document.createElement('div');
spielerElement.setAttribute('style', 'width: 40px; height: 40px; ' +
  'background-color: #F00; position: absolute; top: 0px');
document.querySelector('body').appendChild(spielerElement);
var xPosition = 0;
var bewegeSpieler = function() {
  xPosition = xPosition + 1;
  spielerElement.style.left = xPosition + 'px';
  setTimeout(bewegeSpieler, 15);
};
bewegeSpieler();

Hier wird versucht das Rechteck ca. 60 Mal pro Sekunde zu bewegen. Es kann aber vorkommen, dass der Browser zu stark beansprucht ist und diese Frequenz nicht eingehalten wird.


Deshalb gibt es für Animationen und visuelle Änderungen die Funktion requestAnimationFrame(callback). Diese erwartet nur einen Callback und kümmert sich intern darum wann der Callback das nächste Mal ausgeführt wird.

var spielerElement = document.createElement('div');
spielerElement.setAttribute('style', 'width: 40px; height: 40px; ' +
  'background-color: #F00; position: absolute; top: 0px');
document.querySelector('body').appendChild(spielerElement);
var xPosition = 0;
var frameId = null;
var bewegeSpieler = function() {
  xPosition = xPosition + 1;
  spielerElement.style.left = xPosition + 'px';
  frameId = requestAnimationFrame(bewegeSpieler);
};
bewegeSpieler();


Analog zu clearTimeout() und clearInterval() gibt es die Funktion cancelAnimationFrame(frameId) für die Abmeldung. Diese akzeptiert eine Frame-ID welche wiederum von requestAnimationFrame() zurückgegeben wird.


Tipp: Es ist empfehlenswert Animationen und visuelle Änderungen mit requestAnimationFrame() zu implementieren.