JavaScript-Tutorium:Browser: Unterschied zwischen den Versionen
Zeile 205: | Zeile 205: | ||
=Document Object Model= | =Document Object Model= | ||
Das | 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. | ||
Dabei werden Tags, Texte und Kommentare in Knoten umgewandelt, wobei Tag-Knoten wiederum Unterknoten haben können. | Dabei werden Tags, Texte und Kommentare in Knoten umgewandelt, wobei Tag-Knoten wiederum Unterknoten haben können. | ||
<source lang=" | <source lang="html4strict"> | ||
<html> | <html> | ||
<head> | <head> | ||
Zeile 225: | Zeile 228: | ||
</p> | </p> | ||
<input type="text" value="Eingabe"> | <input type="text" value="Eingabe"> | ||
</body> | </body> | ||
</html> | </html> | ||
</source> | |||
Das Markup wird in folgendes Objektmodell übersetzt: | |||
* Ein '''html'''-Knoten mit den Unterknoten '''head''' und '''body''' | |||
* Ein | * Ein '''head'''-Knoten mit dem Unterknoten '''title''' | ||
* Ein | * Ein '''title'''-Knoten mit einem Text-Unterknoten | ||
* Ein | * Ein '''body'''-Knoten mit den Unterknoten '''p''' und '''input''' | ||
* Ein | * Ein '''p'''-Knoten mit einem Text- und einem Kommentar-Unterknoten | ||
* Ein | * Ein '''input'''-Knoten | ||
* Ein | |||
'''Tipp:''' Achtet man bei HTML auf eine korrekte Einrückung so ist das resultierende Objektmodell meist | '''Tipp:''' Achtet man bei HTML auf eine korrekte Einrückung so ist das resultierende Objektmodell meist gut erkennbar. | ||
===Markup-Korrekturen=== | ===Markup-Korrekturen=== | ||
Bei dem Objektmodell handelt es sich '''nicht''' um exaktes Abbild des originalen Markups. | |||
Der Unterschied wird | Der Unterschied wird klar, wenn man das Resultat von invalidem HTML untersucht. | ||
<source lang=" | <source lang="html4strict"> | ||
<html> | <html> | ||
<head> | <head> | ||
Zeile 261: | Zeile 264: | ||
</body> | </body> | ||
</html> | </html> | ||
</source> | </source> | ||
Der Browser fügt zwischen dem <code>table</code>- und dem <code>tr</code>-Knoten einen <code>tbody</code>-Knoten ein. | |||
==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 allen 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 | |||
Die | 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 282: | Zeile 287: | ||
* 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=== | ||
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"> | <source lang="javascript"> | ||
console.log(document. | console.log(document.documentElement); | ||
</source> | </source> | ||
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 | Element-Objekte besitzen Schnittstellen um auf Eigenschaften des Tags zuzugreifen und diese zu modifizieren. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
Zeile 314: | Zeile 316: | ||
== | ==Erstellen von Elementen== | ||
Neue Element-Objekte werden über die Funktion <code>createElement()</code> eines <code>document</code>-Objekts unter Angabe des Tag-Namens erzeugt. | |||
<source lang="javascript"> | <source lang="javascript"> | ||
var absatz = document.createElement('p'); | var absatz = document.createElement('p'); | ||
Zeile 335: | Zeile 327: | ||
</source> | </source> | ||
Bei der Erstellung wird das neue Element dem | |||
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"> | ||
var block = document.createElement('div'); | var block = document.createElement('div'); | ||
console.log(block.ownerDocument); | console.log(block.ownerDocument); | ||
</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"> | ||
var meinElement = document.createElement('mein-element'); | var meinElement = document.createElement('mein-element'); | ||
console.log(meinElement); | console.log(meinElement); | ||
</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. | ||
Zeile 359: | Zeile 353: | ||
===Vorgefertige Kriterien=== | ===Vorgefertige Kriterien=== | ||
Die | Die ursprünglichen Funktionen zum Suchen von Elementen erlauben wenige festgelegte Kriterien. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var | var spielfeld = document.getElementById('spielfeld'); | ||
console.log( | console.log(spielfeld); | ||
var divElemente = document.getElementsByTagName('div'); | |||
console.log(divElemente); | |||
var | |||
var gegnerElemente = document.getElementsByClassName('gegner'); | |||
console.log(gegnerElemente.length); | |||
</source> | </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. | Das einfachste Kriterium für einen CSS-Selektor ist die Angabe eines Tag-Namen. | ||
Zeile 443: | Zeile 398: | ||
console.log(absaetze); | console.log(absaetze); | ||
</source> | </source> | ||
Die Angabe einer ID eines HTML-Elements erfolgt durch eine vorangestellte Raute (<code>#</code>). | Die Angabe einer ID eines HTML-Elements erfolgt durch eine vorangestellte Raute (<code>#</code>). | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var | var spielfeldElement = document.querySelector('#spielfeld'); | ||
console.log( | console.log(spielfeldElement); | ||
</source> | </source> | ||
</ | Klassen-Selektoren werden durch einen vorangestellten Punkt (<code>.</code>) gekennzeichnet. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var | var gegnerElemente = document.querySelectorAll('.gegner'); | ||
console.log( | console.log(gegnerElemente); | ||
</source> | </source> | ||
Die Mächtigkeit von CSS-Selektoren ergibt sich vor allem durch die beliebige Kombinationsmöglichkeiten von Selektoren. | Die Mächtigkeit von CSS-Selektoren ergibt sich vor allem durch die beliebige Kombinationsmöglichkeiten von Selektoren. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var | var schwierigeGegnerElemente = | ||
console.log( | document.querySelectorAll('.gegner.schwierig'); | ||
console.log(schwierigeGegnerElemente); | |||
</source> | |||
'''Anmerkung 1:''' Die Komplexität einer CSS-Selektorgruppe beeinflusst die Performance einer Suche. | |||
'''Anmerkung | '''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. | Diese Einführung dient lediglich dazu ein grundsätzliches Verständnis für den Umgang mit Selektoren zu vermitteln. | ||
==Hinzufügen von Elementen== | ==Hinzufügen von Elementen== | ||
Neu erstellte Elemente können im Dokument als untergeordnete Knoten mithilfe von <code>appendChild()</code> eingefügt werden. | Neu erstellte Elemente können im Dokument als untergeordnete Knoten mithilfe von <code>appendChild()</code> eingefügt werden. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var | var spielerElement = document.createElement('div'); | ||
document.documentElement.appendChild( | document.documentElement.appendChild(spielerElement); | ||
</source> | |||
Der künftig übergeordnete Knoten muss angesprochen werden, wenn das Element an einer bestimmten Stelle landen soll. | |||
var | <source lang="javascript"> | ||
var | var spielerElement = document.querySelector('div'); | ||
var spielfeld = document.createElement('div'); | |||
spielfeld.appendChild(spielerElement); | |||
</source> | </source> | ||
Zeile 513: | Zeile 454: | ||
==Modifizieren von Elementen== | ==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"> | <source lang="javascript"> | ||
var body = document.querySelector('body'); | var body = document.querySelector('body'); | ||
body.innerHTML = 'Hallo Welt'; | 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 body = document.querySelector('body'); | var body = document.querySelector('body'); | ||
body.innerHTML = '<p>Hallo <strong>Welt!</strong></p>'; | body.innerHTML = '<p>Hallo <strong>Welt!</strong></p>'; | ||
</source> | |||
===Ändern von Attributen=== | ===Ändern von Attributen=== | ||
Für den Zugriff auf die Attribute eines HTML-Elements gibt es die Funktionen <code>getAttribute()</code> und <code>setAttribute()</code>. | Für den Zugriff auf die Attribute eines HTML-Elements gibt es die Funktionen <code>getAttribute()</code> und <code>setAttribute()</code>. | ||
Zeile 555: | Zeile 485: | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var | var spielerElement = document.createElement('div'); | ||
spielerElement.innerHTML = '_|_'; | |||
spielerElement.setAttribute('class', 'spieler'); | |||
document.querySelector('body').appendChild( | document.querySelector('body').appendChild(spielerElement); | ||
console.log( | console.log(spielerElement.getAttribute('class')); | ||
</source> | </source> | ||
'''Anmerkung:''' | '''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. | 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 | var spielerElement = document.querySelector('.spieler'); | ||
spielerElement.classList.add('unbesiegbar'); | |||
</source> | |||
===Ändern von CSS-Eigenschaften=== | ===Ändern von CSS-Eigenschaften=== | ||
Neben dem Document Object Model existiert außerdem der [http://dev.w3.org/csswg/cssom/ CSS Object Model Standard] | |||
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. | 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. | Ein jedes DOM-Element vom Typ <code>Element</code> besitzt eine Eigenschaft <code>style</code> welche alle CSS-Eigenschaften beherbergt. | ||
Zeile 591: | Zeile 516: | ||
console.log(body.style.color); | console.log(body.style.color); | ||
console.log(body.style.padding); | console.log(body.style.padding); | ||
</source> | |||
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. | 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. | Die Eigenschaften können ebenso beliebig modifiziert werden, was zu einer sofortigen Anpassung der Darstellung führt. | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var body = document.querySelector('body'); | var body = document.querySelector('body'); | ||
body.style.backgroundColor = '#AAF'; | body.style.backgroundColor = '#AAF'; | ||
</source> | |||
==Löschen von Elementen== | ==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. | Ebenso wie neue Elemente einem Dokument hinzugefügt werden können, so können diese oder bestehende auch gelöscht werden. | ||
Zeile 610: | Zeile 538: | ||
<source lang="javascript"> | <source lang="javascript"> | ||
var | var spielfeld = document.querySelector('.spielfeld'); | ||
var | var spielerElement = spielfeld.querySelector('.spieler'); | ||
spielfeld.removeChild(spielerElement); | |||
</source> | |||
Ist einem der übergeordnete Knoten nicht bekannt so kann diesen mithilfe der Knotenverweise ermitteln. | |||
<source lang="javascript"> | <source lang="javascript"> | ||
var entferneKnoten = function(knoten) { | var entferneKnoten = function(knoten) { | ||
Zeile 625: | Zeile 554: | ||
} | } | ||
var | var spielerElement = document.querySelector('.spieler'); | ||
entferneKnoten( | entferneKnoten(spielerElement); | ||
</source> | </source> | ||
=DOM Events= | =DOM Events= |
Version vom 14. Oktober 2015, 23:54 Uhr
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.
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ülltdocument.querySelectorAll()
liefert alle Elemente zurück welche die Kriterien erfüllen in Form einerNode
-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 eine sequentiell ablaufende Folge von Instruktionen. Ergeben sich Informationen jedoch nicht deterministisch zur Laufzeit bietet sich die ereignisorientierte Programmierung an. Hierbei wird nicht mehr aktiv auf eine Information gewartet, das Programm wird benachrichtigt wenn ein Ereignis eintritt. Somit gibt es nicht nur einen einzelnen Kontrollfluss, sondern beliebig viele welche sogar auch parallel ablaufen können.
Ereignisse im Browser
Bei der Betrachtung von und der Interaktion mit Websites im Browser ergeben sich sehr viele unterschiedliche Ereignisse. Zunächst wird die HTML-Seite geladen und gerendert, danach geschieht dies für alle anderen referenzierten Medien. Diese Vorgänge lösen bereits nicht deterministische Ereignisse aus welche alleine durch den Browser verursacht werden. So gibt es für die meisten Medien ein Ereignis welches beschreibt dass diese fertig geladen und gerendert wurden.
Sobald der Browser beginnt eine Web-Seite zu rendern können Ereignisse von Seiten des Benutzers eintreten. Nahezu jede Benutzerinteraktion löst im Browser ein Ereignis aus welches wiederum in weiteren Aktionen resultiert. Klickt der Benutzer auf einen Link so ist das ein Ereignis welches zur Folge hat dass die geklickte URL geladen wird. Wird ein Scroll-Balken vom Benutzer bewegt ergibt sich daraus ein Ereignis welches den Browser in Folge scrollen lässt.
Zu den wichtigsten Arten von durch den Benutzer ausgelösten Ereignissen zählen:
- Mausbewegungen und -klicks
- Tastatureingaben
- Scrolling
Eine vollständige Auflistung der möglichen Ereignisse im Browser finden Sie auf dieser MDN-Seite. Nebem dem Browser und dem Benutzer gibt es auch andere Auslöser wie zum Beispiel mit der Website verbundene Server/Backends.
Aufbau eines Ereignisses
Im Sinne einer standardisierten Ereignisverarbeitung besitzt ein im Browser auftretendes Ereignis immer drei folgende Merkmale:
- Ereignis-Name: eine Zeichenkette die das Ereignis in der Regel in der Gegenwart beschreibt, z.B. "click" oder "mousemove"
- Ereignis-Daten: ein Objekt mit einer pro Ereignis festgelegten Struktur, welches die wichtigsten Ereignisdaten enthält
- Ereignis-Ziel: ein Objekt das als Ziel des Ereignis festgelegt wird; in der Regel handelt es sich um DOM-Elemente
Beispiel für ein solches Ereignis: 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 sogenannten Capturing-Phase wird zuerst ein Ereignis mit dem obersten Knoten im Dokument als Ziel erzeugt.
Dabei handelt es sich in der Regel um das document
-Objekt, welches alle andere DOM-Objekte als Unterknoten enthält.
Das wird schrittweise wiederholt für jeden untergeordneten Knoten auf dem Pfad zum eigentlichen Ziel des Ereignisses.
+ window (optional) - Ereignis mit window als aktueller Ziel
+ document - Ereignis mit body als aktueller Ziel
+ body - Ereignis mit body als aktueller Ziel
+ p (ursprüngliches Ziel)
Anmerkung: Bei manchen Ereignissen wird das window
-Objekt als oberstes Objekt in die Hierarchie der Ziele mit aufgenommen.
Target-Phase
Ist der Browser nun beim ursprünglichen Ziel angelangt wird in Phase 2 gewechselt, der sogenannten Target-Phase. Dabei wird ein einzelnes Ereignis erzeugt, 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 erzeugt.
Anmerkung: In Bibliotheken wie jQuery hat man nur Zugriff auf die Bubbling-Phase, nicht auf die Capturing-Phase.
Browser-Unterschiede
Die Capturing- und Bubbling-Phasen bilden in ihrer Implementierung die Tatsache ab dass wenn ein Ereignis auf einem Element stattfindet, dieses Ereignis ebenso für das Element umgebende Elemente gültig ist. Beispiel: Wird ein Mensch in einem Krankenhaus geboren, so wird dieser auch in einer Stadt geboren, in einem Land geboren.
Einer der beiden oben beschriebenen Phasen wäre allerdings vollkommen ausreichend für die Ereignisvearbeitung. Aufgrund von unterschiedlichen Implementierungen der Browser-Hersteller 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 abgesehen siner 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 außerdem 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.
Durch einen Aufruf wird für ein Ereignis auf einem Ziel ein sogenannter Event-Listener in Form einer Funktion angemeldet.
Jedes Mal wenn das Ereignis auftritt wird die Callback-Funktion aufgerufen und das entsprechende Ereignis-Objekt übergeben.
var informiereMichWennSeiteGeladenIst = function(event) {
console.log('Die Seite ist nun fertig geladen');
console.log(event);
}
document.addEventListener('DOMContentLoaded', informiereMichWennSeiteGeladenIst, false);
document.querySelector('body').innerHTML = '<p><a id="link1" href="#">Link 1</a></p>';
var link = document.querySelector('#link1');
var informiereMichWennLinkGeklicktWird = function(event) {
console.log('Der Link wurde geklickt');
};
link.addEventListener('click', informiereMichWennLinkGeklicktWird);
Aufgrund der Phasen der Ereignispropagierung kann man Ereignisse global verarbeiten unabhängig davon wo sie stattgefunden haben.
document.querySelector('body').innerHTML = '<p><a href="#">Link 1</a><br><a href="#">Link 2</a></p>';
var informiereMichWennGeklicktWird = function(event) {
console.log('Benutzer hat irgendwo auf der Seite geklickt');
};
document.addEventListener('click', informiereMichWennGeklicktWird);
Callbacks
Ein Callback ist eine Rückruffunktion welche an ein andere Funktion übergeben wird und zu späteren Zeitpunkten ggf. ausgeführt wird.
var informiereMichWennMausBewegtWird = function() {
console.log('Benutzer hat geklickt');
};
document.addEventListener('mousemove', informiereMichWennMausBewegtWird);
Es ist jedoch nicht garantiert dass eine übergebene Callback-Funktion überhaupt jemals ausgeführt wird.
var informiereMichFallsEsOktoberIst = function(event) {
console.log('Es ist Oktober');
};
var istEsOktober = function(callback) {
if (new Date().getMonth() == 9) callback();
};
istEsOktober(informiereMichFallsEsOktoberIst);
Callback-Mechanismen unterstützen den Umgang mit nicht deterministischen und asynchronen Ereignissen in JavaScript.
Ereignis-Objekte
Jeder Event-Listener-Callback-Funktion 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 Phasen die ein Ereignis durchlaufen kann unterscheidet man zwischen ursprünglichem und aktuellem Ziel.Das aktuelle Ziel ist das auf welchem das Ereignis aktuell ausgelöst wird 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 zuvor angemeldeter Event-Listener wieder von einem Objekt entfernt werden.
Diese Funktion benötigt im Gegensatz zu addEventListener
nur zwei Argumente: Den Ereignis-Namen und die Event-Listener-Funktion.
var loggeMausBewegungen = function(event) {
console.log(event);
};
document.addEventListener('mousemove', loggeMausBewegungen);
document.removeEventListener('mousemove', loggeMausBewegungen);
Standardverhalten
Nachdem alle Ereignisse erzeugt und verarbeitet wurden führt der Browser gegegebenfalls eine sogenannte "Default Action" aus. Im Wesentlichen ist eine solche Aktion 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 zum Beispiel 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 referenzierte Inhalte bereits geladen wurden, wie z.B. Bilder.
Das Ereignis wird direkt und 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 direkt und 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) komplett 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 erzeugt.
Das Ereignis mouseover
wird erzeugt wenn die Maus in das Element eindringt, mouseout
wenn das Element 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 auf einem DOM-Element durch den Benutzer werden die Ereignisse keydown
, keyup
und keypress
ausgelöst.
Die drei unterschiedlichen Ereignisse beschreiben die unterschiedlichen Phasen einer einzelnen Tastatureingabe.
document.addEventListener('keydown', function(event) {
console.log('Der Benutzer hat eine Taste gedrückt');
});
document.addEventListener('keypress', function(event) {
console.log('Der Benutzer hat eine Taste betätigt');
});
document.addEventListener('keyup', function(event) {
console.log('Der Benutzer hat eine Taste losgelassen');
});
Wiederverwendung
Mithilfe von DOM Events kann auch ein genereller, wiederverwendbarer 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 EventBasiertesInterval = function(delay) {
var self = this;
self.eventDispatcher = new EventDispatcher();
self._zaehler = 0;
setInterval(function() {
self.eventDispatcher.dispatchEvent('interval', self._zaehler++);
}, delay);
}
var interval = new EventBasiertesInterval(500);
interval.eventDispatcher.addEventListener('interval', function(event) {
console.log('interval ', event.detail);
});
Timer
Timer in JavaScript dienen dazu um CPU-intensive Berechnungen in mehrere Teile zu spalten und um Animation zu erzeugen.
JavaScript-Thread
Der Browser stellt für jede dargestellte Web-Seite einen einzelnen Thread zur Verfügung. Dieser kümmert sich um das Rendering der Web-Seite, die Verarbeitung von Events und die Ausführung von JavaScript. Mit Ausnahme des HTML5-Standards der WebWorker gibt es dementsprechend in JavaScript kein Multithreading.
Intern bestitzt der Thread eine Liste an Aufgaben die er stets als nächstes ausführen wird, der sogenannten Queue. Wird eine Web-Seite zum ersten Mal geladen verursacht dies gleich mehrere 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 so auch die Ausführung anderer Aufgaben beeinflussen. Nimmt ein Stück JavaScript-Code die CPU zu lange in Anspruch kann der Browser z.B. keine Benutzereingaben mehr vearbeiten. Um dies zu vermeiden ist ein Zeit- bzw. Code-Limit für JavaScript eingebaut bevor der Browser eine Warnung ausgibt.
var programmStart = new Date();
var wartezeitInMs = 5000;
while (new Date() - programmStart < wartezeitInMs) {
// nichts tun
};
Timeouts
Mithilfe der Funktion setTimeout(callback, delayMs)
kann man einen einmalig auszuführenden Timer auf der Thread-Queue anmelden.
Die übergebene Callback-Funktion wird ungefähr nach einer Wartezeit mit Länge der übergebenen Dauer ausgeführt.
Es werden Einträge in der Thread-Queue erzeugt die überprüfen ob es Zeit ist den Callback auszuführen und das dann ggf. tun. 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('Nachricht ' + vergangeneZeit + 'ms nach Programmstart');
};
setTimeout(loggeNachricht, 500);
while (new Date() - programmStart < 1000) {
// nichts tun
}
Eine Wartezeit von 0 Millisekunden heißt, dass der Interpreter die Callback-Funktion so bald wie möglich ausführen soll.
Abhängig von der Browser-Implementierung kann es sein dass die minimale Wartezeit aber bis zu 15 Millisekunden beträgt.
var programmStart = new Date();
var loggeNachricht = function() {
var vergangeneZeit = new Date() - programmStart;
console.log('Nachricht ' + 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();
Soll die wiederholte Ausführung beendet werden kann dies durch einen Aufruf von clearTimeout(timeoutId)
erreicht werden.
Dabei wird der Eintrag mit gegebener Timeout-ID aus der Thread-Queue gelöscht wodurch der angemeldete Timer nicht ausgeführt wird.
Die notwendige Timeout-ID für die Abmeldung wird beim Anmelden 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
Außerdem existiert die Funktion setInterval(callback, delayMs)
welche einen wiederholt auszuführenden Timer regsitriert.
Es wird ebenso eine Callback-Funktion erwartet und eine Wartezeit zwischen den einzelnen Ausführungen des Callbacks.
var loggeAktuelleZeit = function() {
console.log(new Date());
};
setInterval(loggeAktuelleZeit, 15);
Analog zu setTimeout()
wird eine Interval-ID zurückgegeben, welche für die Abmeldung mittels clearInterval()
genutzt werden kann.
var loggeAktuelleZeit = function() {
console.log(new Date());
};
var intervalId = setInterval(loggeAktuelleZeit, 15);
setTimeout(function() {
clearInterval(intervalId);
}, 2500);
Zunächst wirkt setInterval()
komfortabler, da der Callback nicht durch die wiederholende Timer-Registrierung verunreinigt wird.
Jedoch besteht bei dieser Funktion der Nachteil dass die Messung der Wartezeit vor Ausführung des Callbacks beginnt.
Registriert man eine Aufgabe die länger braucht als die definierte Wartezeit wird der JavaScript-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 Browser zwar noch CPU-Zeit zur Verfügung hat ist er fast nur damit beschäftigt die überfälligen Callbacks auszuführen.
Aufgrund dessen ist es sinnvoller setTimeout()
für wiederholende Aufgaben zu verwenden deren Dauer nicht exakt vorhersehbar ist.
Aufgrund der vielen verschiedenen Aufgaben des JavaScript-Threads ist eine exakte Wartezeit bei Timern nicht garantiert. Sollen Werte abhängig von der vergangenen Zeit verändern sollten Datum-Objekte in die Berechnungen mit einbezogen werden.
var objekt = {position: 0, geschwindigkeitProSekunde: 1};
var letzterTimerAufruf = new Date();
var bewegeObjekt = function() {
var vergangeneZeitInMs = new Date() - letzterTimerAufruf;
objekt.position += geschwindigkeitProSekunde * (vergangeneZeitInMs / 1000);
console.log('Aktuelle Position: ' + objekt.position);
letzterTimerAufruf = new Date();
setTimeout(bewegeObjekt, 1000);
};
bewegeObjekt();
Animation Frames
Mithilfe von setTimeout()
ist es möglich neben wiederholenden Aufgaben auch Animationen im Browser zu implementieren.
Dazu werden in der Callback-Funktion einfach die zu bewegenden Elemente entsprechend in ihrer Form und Position verändert.
var div = document.createElement('div');
div.setAttribute('style', 'width: 40px; height: 40px; background-color: #F00; position: absolute; top: 0px');
document.querySelector('body').appendChild(div);
var xPosition = 0;
var bewegeRechteck = function() {
xPosition = xPosition + 1;
div.style.left = xPosition + 'px';
setTimeout(bewegeRechteck, 15);
};
bewegeRechteck();
In diesem Fall wird versucht dass Rechteck ca. 60 Mal in der Sekunde zu bewegen, also eine Bildwiederholrate von 60 simuliert.
Es kann allerdings vorkommen dass der Browser viele anderen Dingen zu tun hat und ihn ein solcher Timer zusätzlich sehr beansprucht.
Deshalb gibt es speziell für Animationen und visuelle Änderungen die Funktion requestAnimationFrame(callback)
.
Diese erwartet lediglich einen Callback und kümmert sich intern darum wann der Callback das nächste Mal ausgeführt wird.
var div = document.createElement('div');
div.setAttribute('style', 'width: 40px; height: 40px; background-color: #F00; position: absolute; top: 0px');
document.querySelector('body').appendChild(div);
var xPosition = 0;
var bewegeRechteck = function() {
xPosition = xPosition + 1;
div.style.left = xPosition + 'px';
requestAnimationFrame(bewegeRechteck, 15);
};
bewegeRechteck();
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.
Anmerkung: Es ist empfehlenswert Animation mit requestAnimationFrame
zu implementieren.