-
Notifications
You must be signed in to change notification settings - Fork 17
/
Copy pathorganisation-ueberblick.html
347 lines (239 loc) · 20.8 KB
/
organisation-ueberblick.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Organisation von JavaScripten: Voraussetzungen und Überblick</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="js-doku.css">
</head>
<body>
<div id="nav">
<p>Hier entsteht eine <strong>JavaScript-Dokumentation</strong> von <a href="https://molily.de/">molily</a>. Derzeit ist sie noch lückenhaft, wächst aber nach und nach. Kommentare und Feedback werden gerne per <a href="mailto:[email protected]">E-Mail</a> entgegen genommen.</p>
<p class="contents-link"><a href="./">Zum Inhaltsverzeichnis</a></p>
</div>
<h1>Organisation von JavaScripten: Voraussetzungen und Überblick</h1>
<div class="section" id="einleitung">
<h2>Einleitung: Anforderungen an die heutige JavaScript-Programmierung</h2>
<div class="sidebox">
<p>Wie sehen wohlgestrukturierte, umfangreiche JavaScript aus?</p>
</div>
<p>Dieser Teil der Einführung in JavaScript richtet sich an Web- und Anwendungsentwickler, die umfangreiche und komplexe JavaScripte schreiben. Mittlerweile ermöglichen Frameworks wie jQuery einen schnellen Einstieg in das DOM-Scripting. Wie gut der Code in diesen kleinen Anwendungsfällen strukturiert ist, spielt keine entscheidende Rolle. Das Framework gibt bereits eine Grundstruktur vor, die für diese Zwecke ausreicht.</p>
<p>Wenn Scripte jedoch über ein simples DOM-Scripting hinausgehen und ganze Webanwendungen entstehen, stellen sich verschiedene Fragen: Wie lässt sich die komplexe Funktionalität geordnet in JavaScript implementieren? Welche Möglichkeiten der objektorientierten Programmierung in JavaScript gibt es? Wie lassen sich Datenmodelle in JavaScript angemessen umsetzen?</p>
<p>Eine sinnvolle Struktur wird zur Anforderung ebenso wie die Optimierung der Performance. Wird der JavaScript-Code mehrere tausend Zeilen lang, so soll er wartbar, übersichtlich und testbar bleiben. Er soll modular sein sowie wenig Redundanzen besitzen. Verschiedene Programmierer sollen gleichzeitig daran arbeiten können. Wenn das Script veröffentlicht werden soll, sodass es Fremde auf Ihren Sites verwenden, muss es in verschiedenen, unberechenbaren Umgebungen robust arbeiten.</p>
<p>Diese Einführung soll Strategien vorstellen, um diesen Herausforderungen zu begegnen. Bevor wir uns konkreten JavaScript-Fragen widmen, soll zunächst erörtert werden, wann und wie JavaScript sinnvoll eingesetzt wird. Dies hat direkte Konsequenzen auf die Struktur und Arbeitsweise der JavaScripte. Deshalb ist es bereits auf dieser Ebene wichtig, die richtigen Weichen zu stellen. Alle weiteren Tipps gehen davon aus, dass Sie gemäß diesen Prinzipen arbeiten.</p>
</div>
<div class="section" id="schichtenmodell">
<h2>Das Schichtenmodell: Trennung von Inhalt, Präsentation und Verhalten</h2>
<div class="sidebox">
<p>Inhalte werden sinnvoll ausgezeichnet. Für die Struktur ist HTML zuständig, die Präsentation wird in Stylesheets ausgelagert.</p>
</div>
<p>Im modernen Webdesign kommt den Webtechniken HTML, CSS und JavaScript jeweils eine bestimmte Rolle zu. HTML soll die Texte bedeutungsvoll strukturieren, indem z.B. Überschriften, Listen, Absätze, Datentabellen, Abschnitte, Hervorhebungen, Zitate usw. als solche ausgezeichnet werden. CSS definiert die Regeln für die Darstellung dieser Inhalte, sei es auf einem Desktop-Bildschirm, auf einem mobilen Gerät oder beim Ausdrucken.</p>
<p>Um eine Website effizient zu entwickeln sowie sie nachträglich mit geringem Aufwand pflegen zu können, sollen diese beiden Aufgaben strikt voneinander getrennt werden: Im HTML-Code werden keine Angaben zur Präsentation gemacht. Im Stylesheet befinden sich demnach alle Angaben zur Präsentation in möglichst kompakter Weise. Dadurch müssen im HTML-Code nur genau soviele Angriffspunkte für CSS-Selektoren gesetzt werden, wie gerade nötig sind (z.B. zusätzliche <code>div</code>- oder <code>span</code>-Elemente sowie <code>id</code>- und <code>class</code>-Attribute). Ein und dasselbe Dokument kann auf diese Weise durch den Wechsel des Stylesheets ein völlig anderes Layout bekommen. Aber auch ganz ohne Stylesheet sind die Inhalte noch sinnvoll strukturiert und die Inhalte zugänglich.
</div>
<div class="section" id="unobtrusivejs">
<h2>Erste Schritte zum Unobtrusive JavaScript</h2>
<div class="sidebox">
<p>Unaufdringliche JavaScripte sind getrennt vom HTML-Code. Sie starten selbstständig, greifen auf das Dokument zu und fügen Funktionalität hinzu.</p>
</div>
<p>JavaScript kommt im Schichtenmodell die Aufgabe zu, dem Dokument <strong>Verhalten</strong> (englisch Behaviour) hinzuzufügen. Damit ist gemeint, dass das Dokument auf gewisse Anwenderereignisse reagiert und z.B. Änderungen im Dokument vornimmt.
<p>Die Grundlage für eine sinnvolle JavaScript-Programmierung ist die <strong>Auslagerung des JavaScript-Codes</strong>: Im HTML-Code sollte sich kein JavaScript in Form von Event-Handler-Attributen befinden (<code>onload</code>, <code>onclick</code>, <code>onmouseover</code> usw.). Stattdessen werden Elemente, denen ein bestimmtes Verhalten hinzugefügt werden soll, falls nötig mit einer Klasse oder ID markiert, um die Elemente eindeutung adressieren zu können. Die nötige Interaktivität wird dem Dokument automatisch hinzugefügt. Beim Ladens des Dokuments wird das Script aktiv, initialisiert sich und startet die Ereignisüberwachung an den betreffenden Elementen. Diese Anwendung von JavaScript nennt sich <a href="http://ichwill.net/">Unobtrusive JavaScript</a>, »unaufdringliches« JavaScript.</p>
<div class="section" id="javascript-auslagern">
<h3>JavaScript auslagern und extern am Dokumentende einbinden</h3>
<p>Vermeiden Sie auf ins HTML eingebetteten JavaScript-Code. Verzichten Sie auf Inline-Event-Handler und <code>script</code>-Elemente, die direkt JavaScript enthalten. Binden Sie externe Scripte mit <code><script src="…"></script></code> ein. Aus Performance-Gründen sollten Sie dieses <code>script</code>-Element ans Dokumentende setzen, direkt vor den schließenden <code></body></code>-Tag.</p>
</div>
<div class="section" id="domready">
<h3>Scripte bei DOM Ready initialisieren</h3>
<p>Initialisieren Sie Ihre Script zu dem Zeitpunkt, wenn das HTML-Dokument eingelesen wurde und das DOM vollständig verfügbar ist. Dieser Zeitpunkt wird üblicherweise <strong>DOM ready</strong> genannt. Siehe <a href="event-handling-onload.html">Scripte beim Laden des Dokuments ausführen</a>. Die üblichen JavaScript-Frameworks stellen dafür Methoden bereit. Intern arbeiten diese hauptsächlich mit dem <code>DOMContentLoaded</code>-Event. Wenn Sie kein Framework verwenden, gibt es auch lose Helferfunktionen für diesen Zweck, z.B. <a href="http://javascript.nwbox.com/ContentLoaded/contentloaded.js">ContentLoaded von Diego Perini</a>.</p>
<p>Beispiel jQuery:</p>
<pre>
$(function () {
// …
});
</pre>
<p>Übergabe einer Funktion über ihren Namen:</p>
<pre>
$(funktion);
</pre>
</div>
<div class="section" id="selektor-engines">
<h3>DOM-Zugriffe über Selektor-Engines</h3>
<p>Nutzen Sie JavaScript-Selektor-Engines, um auf das Dokument über CSS-artige Selektoren zuzugreifen und Elemente auszuwählen. Alle verbreiteten Frameworks bringen solche mit, sie sind aber auch separat erhältlich (z.B. <a href="http://sizzlejs.com/">Sizzle</a>).</p>
<p>Beispiel jQuery:</p>
<pre>
$(function () {
// Liefert alle li-Elemente im Element mit der ID produktliste
$('#produktliste li')
});
</pre>
</div>
<div class="section" id="event-handling">
<h3>Zeitgemäßes Event-Handling</h3>
<p>Nach den Ansprechen der Elemente können Sie Event-Handler registrieren. Dazu benötigen Sie eine leistungsfähige Event-Handling-Komponente, die sicherstellt, dass mehreren Handler für einen Typ registriert werden können und die Ihnen die Event-Verarbeitung durch Nivellierung von Browserunterschieden vereinfacht. Eine solche ist in den üblichen Frameworks eingebaut, alternativ können Sie eine lose <a href="event-handling-fortgeschritten.html#flexibles-addevent">addEvent-Helferfunktionen</a> nutzen.
<p>Beispiel jQuery:</p>
<pre>
$(function () {
$('#produktliste li').click(function (e) {
// … Verarbeite Ereignis …
});
});
</pre>
<p>Zum effizienten Event-Handling gehört <a href="https://molily.de/js/event-handling-effizient.html">Event Delegation</a> hinzu. Die Grundidee ist, dass Ereignisse bei einer ganzen Schar von Elementen durch ein darüberliegendes Element verarbeitet werden. Dazu macht man sich das Event Bubbling und Event Capturing zunutze. Beispielsweise jQuery bietet dafür die Methode <a href="http://api.jquery.com/on/#direct-and-delegated-events">on()</a>.</p>
</div>
</div>
<div class="section" id="oop">
<h2>Objektorientierte Programmierung (OOP) in JavaScript</h2>
<p>JavaScript zieht viele Programmieranfänger an und ist für viele Webautoren die erste Programmiersprache, mit der sie aktiv in Kontakt kommen.
Für sie bleibt die genaue Funktionsweise von JavaScript zunächst unzugänglich. Sie treffen auf Fallstricke und schaffen es nicht, JavaScript zu bändigen. Aber auch professionelle Software-Entwickler mit fundierten Kenntnissen anderer Programmiersprachen geraten an JavaScript. Sie sind nicht weniger verwirrt und verzweifeln, weil sie in JavaScript nicht Strukturen wiederfinden, die ihnen in anderen Sprachen Orientierung bieten.</p>
<div class="section" id="klassen">
<h3>Klassenbasierte Objektorientierung in anderen Programmiersprachen</h3>
<div class="sidebox">
<p>OOP in vielen anderen Programmiersprachen läuft über Klassen, die Kapselung und vieles mehr erlauben. Zusammenhängende Klassen werden in Namensräumen bzw. Modulen gruppiert.</p>
</div>
<p>Viele verbreitete Programmiersprachen arbeiten zumindest teilweise objektorientiert und besitzen ausgereifte konventielle Konzepte zur Strukturierung von Programmen. Das sind vor allem <dfn>Klassen</dfn> und ferner <dfn>Module</dfn>, <dfn>Pakete</dfn> bzw. <dfn>Namensräume</dfn>.</p>
<p>Klassen werden üblicherweise mittels einer Deklaration beschrieben und können daraufhin verwendet werden. Sie bestehen aus Konstruktoren, Methoden und Eigenschaften. Sie können von anderen Klassen erben und diese erweitern. Von Klassen lassen sich beliebig viele Instanzen anlegen – oder nur eine Instanz im Falle von Singletons. Die Sichtbarkeit bzw. Verfügbarkeit von Methoden und Eigenschaften kann über Schlüsselwörter geregelt werden. So wird z.B. zwischen öffentlichen und privaten Eigenschaften und Methoden unterschieden. Eigenschaften können als nur lesbare Konstanten definiert werden. Darüber hinaus erlauben einige Programmiersprachen die Definition von Interfaces, ein Anforderungskatalog mit Methoden, die eine Klasse bereitstellen muss. Verbreitet sind ferner statische Methoden (Klassenmethoden) sowie Getter- und Setter-Methoden, die beim Lesen bzw. Schreiben von Eigenschaften aufgerufen werden.</p>
<p>Diese konventionelle klassenbasierte OOP sieht beispielsweise in PHP 5.3 folgendermaßen aus:</p>
<pre>
namespace Beispielnamensraum;
interface IBeispielInterface
{
public function oeffentlicheMethode();
}
class Beispielklasse implements IBeispielInterface
{
function __construct() {
echo "Konstruktor\n";
}
public $oeffentlich = 'Öffentliche Eigenschaft';
private $privat = 'Private Eigenschaft';
public function oeffentlicheMethode() {
echo "Öffentliche Methode\n";
$this->privateMethode();
}
private function privateMethode() {
echo "Private Methode\n";
echo $this->privat . "\n";
}
const konstante = 'Klassenkonstante';
public static $statisch = 'Statische Eigenschaft (Klasseneigenschaft)';
public static function statischeMethode() {
echo "Statische Methode (Klassenmethode)\n";
echo self::$statisch . "\n";
echo self::konstante . "\n";
}
}
class AbgeleiteteKlasse extends Beispielklasse
{
function __construct() {
parent::__construct();
print "Konstruktor von AbgeleiteteKlasse\n";
}
public function zusatzmethode() {
echo "Zusatzmethode von AbgeleiteteKlasse\n";
parent::oeffentlicheMethode();
}
}
$instanz = new Beispielklasse();
$instanz->oeffentlicheMethode();
echo $instanz->oeffentlich . "\n";
echo Beispielklasse::statischeMethode();
$instanz2 = new AbgeleiteteKlasse();
$instanz2->zusatzmethode();
</pre>
<p>Dieses Beispiel soll hier nicht näher beschrieben werden, sondern nur den Hintergrund illustrieren für diejenigen, die solche oder ähnliche klassenbasierte OOP bereits kennen.</p>
<p>Die besagten Konzepte bilden in eine Richtschnur: Hält sich ein Programmierer an diese Konventionen, so ist mehr oder weniger garantiert, dass das Programm von anderen grob verstanden und wiederverwendet werden kann und nicht mit anderen Programmen in die Quere kommt. Selbst Sprachen, die nicht konsequent objektorientiert arbeiten, bekommen eine einheitliche Struktur dadurch, dass die Programmierer ihren Code in Namensräumen und Klassen organisieren.</p>
</div>
<div class="section" id="oop-js">
<h3>JavaScript ist nicht fixiert auf Klassen</h3>
<div class="sidebox">
<p>JavaScript bietet keine Patentlösungen, sondern erlaubt verschiedene Programmier­paradigmen. Man muss selbst geeignete Techniken zur Strukturierung wählen.</p>
</div>
<p>ECMAScript 3, der JavaScript zugrunde liegende Webstandard, besitzt diese Features nicht. Der Nachfolger ECMAScript 5 verbessert die eigentümlichen Fähigkeiten, bietet etwa Methoden zur Datenkapselung. Erst ECMAScript 6 führt Klassendeklarationen ein, die jedoch lediglich eine Kurzschreibweise für <a href="organisation-instanzen.html">Konstruktoren und Prototypen</a> ist und sich stark von der klassenbasierten Objektorientierung anderer Programmiersprachen unterscheidet.</p>
<p>JavaScript bringt von Haus aus keine »einzig wahre« Programmiertechnik für objektorientierte Programmierung mit. Das kann man als Nachteil auffassen, aber auch als Vorteil. Weil JavaScript ein vorgegebenes Gerüst zu fehlen scheint, fehlt Einsteigern die Orientierung. Es gibt nicht den einen vordefinierten Weg, ein Programme in JavaScript zu strukturieren. Stattdessen muss man sich selbst über die sinnvolle Organisation von JavaScripten Gedanken machen.</p>
<p>Glücklicherweise ist JavaScript so leistungsfähig, dass sich die Strukturen wie Klassen und Namensräume bzw. Module durchaus umsetzen lassen. Die Klassendeklarationen in ECMAScript 6 vereinfacht den Einstieg für diejenigen, die bereits andere, klassenbasierte Programmiersprachen beherrschen. Zusatzbibliotheken ermöglichen die Anwendung komplexere OOP-Konzepte in JavaScript.</p>
<p>Dabei sollten allerdings nicht die Eigenarten und besonderen Fähigkeiten von JavaScript vergessen werden. Im Gegensatz zu manchen streng klassenbasierten Sprachen ist JavaScript äußerst dynamisch und besitzt funktionale Aspekte. JavaScript-Kenner empfehlen, die Fähigkeiten von JavaScript zu nutzen, anstatt bloß klassenbasierte OOP überzustülpen. Mit diesen Möglichkeiten ist JavaScript nicht unbedingt schlechter und defizitär – es ist lediglich ein anderer, nicht minder interessanter und brauchbarer Ansatz.</p>
</div>
</div>
<div class="section" id="grundpfeiler">
<h2>Grundpfeiler der fortgeschrittenen JavaScript-Programmierung</h2>
<div class="sidebox">
<p>JavaScript ist eine objektorientierte, dynamische Sprache. Funktionen und Prototypen machen sie enorm leistungsfähig.</p>
</div>
<p>Die Grundlage von ECMAScript 3 sind Objekte, die zur Laufzeit beliebig um Eigenschaften und Methoden ergänzt werden können. Ein JavaScript-Objekt ist eine ungeordnete Liste, in der String-Schlüsseln beliebige Werte zugeordnet werden. Diese dynamischen, erweiterbaren Objekte ermöglicht eine Strukturierung von Programmen. Sie dienen, wie wir später sehen werden, als vielseitiges Mittel zur Gruppierung und sind insofern mit Hashes, Namensräumen, Singletons bzw. Klassen mit statischen Methoden in anderen Sprachen vergleichbar.</p>
<p>Die wirklichen Stärken von JavaScript liegen jedoch woanders: <strong>Funktionen</strong>. Dazu Douglas Crockford in dem Vortrag <a href="http://developer.yahoo.com/yui/theater/video.php?v=crockonjs-3">Crockford on JavaScript – Act III: Function the Ultimate</a>:
<blockquote lang="en">
<p><img src="images/crockford-functions.jpg" alt="" style="float:right; margin-left: 2em">
Functions are the very best part of JavaScript. It's where most of the power is, it's where the beauty is. … Function is the key idea in JavaScript. It's what makes it so good and so powerful. In other languages you've got lots of things: you've got methods, classes, constructors, modules, and more. In JavaScript there's just function, and function does all of those things and more. That's not a deficiency, that's actually a wonderful thing — having one thing that can do a lot, and can do it brilliantly, at scale, that's what functions do in this language.</p>
</blockquote>
<p>Crockford weist hier darauf hin, wie vielseitig Funktionen in JavaScript sind und dass sie Aufgaben übernehmen, für die es in anderen Sprachen unterschiedliche Konzepte gibt.</p>
<p>Das folgende Diagramm soll die Säulen der fortgeschrittenen JavaScript-Programmierung illustrieren. Das Fundament bilden dynamische Objekte und <code>Object</code>-Literale, darauf bauen Funktionen auf, welche Scopes erzeugen und als Konstruktoren prototypische Vererbung ermöglichen.</p>
<div class="pillars" id="grundpfeiler-diagramm">
<div class="section base" id="objects">
<div>
<h3>Objekte / <code>Object</code>-Objekte</h3>
<ul>
<li>Alles ist ein Objekt – bis auf Primitives, die sich allerdings wie Objekte verhalten können</li>
<li>Objekte sind in der Regel erweiterbar</li>
<li><code>Object</code>-Objekte sind Allround-Container und Hashes</li>
<li><code>Object</code>-Literale erzeugen mit <code>{ key : value, … }</code></li>
</ul>
</div>
</div><!-- /.base -->
<div class="section pillar first" id="funktionen">
<div>
<h3>Funktionen</h3>
<ul>
<li>Dienen der Codestrukturierung</li>
<li>Objekte erster Klasse: Zur Laufzeit erzeugen, übergeben, zurückgeben, in Variablen speichern</li>
<li>Funktionale Programmierung (z.B. Listenoperationen, Currying, Event-Handler)</li>
<li>Können an Objekte als Methoden gehängt werden</li>
<li>Methoden haben <code>this</code>-Kontext, über <code>call</code> und <code>apply</code> veränderbar (Binding)</li>
<li>Erzeugen Scopes</li>
<li>Können verschachtelt werden (Scope-Chain)</li>
<li>Dienen als Konstruktoren</li>
<li>Besitzen Prototypen für damit erzeugte Objekte (<code>funktion.prototype</code>)</li>
</ul>
</div>
</div><!-- /.pillar -->
<div class="section pillar second" id="scope">
<div>
<h3>Scope</h3>
<ul>
<li>Geltungsbereich für Variablen</li>
<li>Allzweckwerkzeug für Datenverfügbarkeit und Kapselung</li>
<li>Auflösung von Bezeichnern zu Werten erfolgt über die Scope-Chain</li>
<li>Scope-Chain speichert die internen Variablenobjekte von Funktionsaufrufen</li>
<li>Scope-Chain ermöglicht Closures</li>
</ul>
</div>
</div><!-- /.pillar -->
<div class="section pillar third" id="prototypen">
<div>
<h3>Prototypen</h3>
<ul>
<li>Prototypenbasierte Vererbung</li>
<li><code>objekt.eigenschaft</code> wird über die Prototype-Chain aufgelöst</li>
<li>Ein Objekt stellt Funktionalität für ein anderes bereit (Delegation)</li>
<li>Speichereffizientes Erzeugen von Objekten gleicher Funktionalität</li>
<li>Ableiten und Verfeinern von Funktionalität</li>
<li>Ganz normale Objekte, die im Programm und nicht per Deklaration erzeugt werden</li>
<li>Alle Objekte können Prototypen sein</li>
</ul>
</div>
</div><!-- /.pillar -->
</div><!-- /.pillars -->
<p>Dieses Diagramm soll Ihnen einen kompakten Überblick geben. Viele der Begriffe wird diese Einführung im weiteren erläutern, auf andere kann sie nicht eingehen.</p>
</div>
<div class="sequence-navigation">
<p class="next"><a href="organisation-module.html" rel="next">Organisation von JavaScripten: Module und Kapselung</a></p>
<p class="prev"><a href="bibliotheken.html" rel="prev">Bibliotheken und Frameworks</a></p>
</div>
<div id="footer">
<p><strong>JavaScript-Dokumentation</strong> · <a href="./">Zum Inhaltsverzeichnis</a></p>
<p>Autor: <a href="https://molily.de/">molily</a> · Kontakt: <a href="mailto:[email protected]">[email protected]</a></p>
<p>Lizenz: <a rel="license" href="https://creativecommons.org/licenses/by-sa/3.0/de/">Creative Commons Namensnennung - Weitergabe unter gleichen Bedingungen 3.0</a></p>
<p><a href="https://github.com/molily/javascript-einfuehrung">JavaScript-Einführung auf Github</a></p>
<p>Kostenloses Online-Buch und E-Book:<br><a href="https://testing-angular.com" lang="en" hreflang="en">Testing Angular – A Guide to Robust Angular Applications</a> (englisch)</p>
</div>
<script src="js-doku.js"></script>
</body>
</html>