-
Notifications
You must be signed in to change notification settings - Fork 17
/
organisation-instanzen.html
551 lines (392 loc) · 32.5 KB
/
organisation-instanzen.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
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Organisation von JavaScripten: Konstruktoren, Prototypen und Instanzen</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: Konstruktoren, Prototypen und Instanzen</h1>
<div class="section" id="instanzen">
<h2>Objektinstanzen mit Konstruktoren und Prototypen</h2>
<p>Mit <code>Object</code>-Literalen und dem Revealing Module Pattern haben wir das gebaut, was in anderen Programmiersprachen <em>Singleton</em> oder <em>Klasse</em> mit statischen Methoden genannt wird. Ein solches Modul kommt nur einmal vor und kann nur einen internen Status haben. In vielen Fällen ist es jedoch sinnvoll, mehrere Instanzen (Exemplare) eines Objekts zu erzeugen. In vielen anderen Programmiersprachen würde man dazu eine eigene Klasse definieren und davon Instanzen erzeugen.</p>
<p>In JavaScript gibt es wie eingangs erwähnt keine Klassen. Es gibt jedoch <strong>Konstruktor-Funktionen</strong> (kurz: <dfn>Konstruktoren</dfn>) und <strong>prototypische Objekte</strong> (kurz: Prototypen).</p>
<div class="section" id="konstruktoren">
<h3>Konstruktor-Funktionen (Konstruktoren)</h3>
<div class="sidebox">
<p>Konstruktoren sind normale Funktionen, die mit <code>new Funktion</code> aufgerufen werden. Das neu erzeugte Instanzobjekt ist darin über <code>this</code> verfügbar.</p>
</div>
<p>Der Name Konstruktor stammt vom englischen <em>construct</em> = erzeugen, konstruieren, bauen. Eine Konstruktor-Funktion ist demnach ein Erzeuger neuer Objekte. Sie werden sich sicher fragen, wie die Syntax zum Notieren von Konstruktoren lautet. Ein Konstruktur ist jedoch keine besondere Sprachstruktur, sondern erst einmal eine ganz normale Funktion. Zu einem Konstruktor wird sie lediglich dadurch, dass sie mit dem Schlüsselwort <code>new</code> aufgerufen wird.</p>
<p>Wenn eine Funktion mit <code>new</code> aufgerufen wird, wird intern ein neues, leeres <code>Object</code>-Objekt angelegt und die Funktion <a href="organisation-verfuegbarkeit.html">im Kontext dieses Objekts ausgeführt</a>. Das bedeutet, im Konstruktor kann das neue Objekt über <code>this</code> angesprochen werden. Darüber können ihm z.B. Eigenschaften und Methoden hinzugefügt werden.</p>
<p>Intern wird also ein <code>Object</code>-Objekt angelegt, genauso wie wir es zur Strukturierung getan haben. Der Unterschied beim Konstruktor ist, dass auf diese Weise unzählige gleich ausgestattete Objekte, sogenannte <dfn>Instanzen</dfn> erzeugt werden können.</p>
<pre>
// Konstruktorfunktion
<strong>function Konstruktor</strong>() {
// Zugriff auf das neue Objekt über this,
// Hinzufügen der Eigenschaften und Methoden
this.eigenschaft = "wert";
this.methode = function () {
// In den Methoden wird ebenfalls über this auf das Objekt zugegriffen
alert("methode wurde aufgerufen\n" +
"Instanz-Eigenschaft: " + this.eigenschaft);
};
}
// Erzeuge Instanzen
var instanz1 = <strong>new Konstruktor()</strong>;
instanz1.methode();
var instanz2 = <strong>new Konstruktor()</strong>;
instanz2.methode();
// usw.
</pre>
<p>Dieses Beispiel enthält eine Konstruktorfunktion, mithilfe derer zwei Instanzen erzeugt werden. Innerhalb des Konstruktors werden dem leeren Instanzobjekt zwei Eigenschaften hinzugefügt, ein String und eine Funktion.</p>
<p>Als Funktion kann ein Konstruktor Parameter entgegennehmen. Somit können Instanzen mit unterschiedlichen Eigenschaften erzeugt werden:</p>
<pre>
function Katze(name, rasse) {
this.name = name;
this.rasse = rasse;
this.pfoten = 4;
}
var maunzi = new Katze('Maunzi', 'Perserkatze');
</pre>
<p>Die dem Konstruktor übergebenen Parameter werden zu Eigenschaften des Instanzobjekts. Auch wenn verschiedene <code>Katze</code>-Instanzen abweichende Eigenschaftswerte haben können, so ist ihnen allen gemein, dass sie die Eigenschaften <code>name</code> und <code>rasse</code> besitzen. Neben diesen beiden gibt es eine feste Eigenschaft <code>pfoten</code>: Katzen haben (in der Regel) vier Pfoten.</p>
<p>Eigenschaften können nach dem Erzeugen neue Werte bekommen. Der Zugriff von außen auf die Objekteigenschaften erfolgt über das bekannte Schema <code>instanzobjekt.member</code>. Wie gesagt sind Objekte in JavaScript (ECMAScript 3) jederzeit änderbar und erweiterbar.</p>
<pre>
var maunzi = new Katze('Maunzi', 'Perserkatze');
alert(maunzi.name + ' ist eine ' + maunzi.rasse);
maunzi.rasse = 'Siamkatze';
alert(maunzi.name + ' ist neuerdings eine ' + maunzi.rasse);
</pre>
<p>Da diese Eigenschaften von außen zugänglich und schreibbar sind, handelt es sich um <dfn>öffentliche Eigenschaften</dfn>.</p>
</div>
<div class="section" id="prototypen">
<h3>Prototypische Objekte (Prototypen)</h3>
<div class="sidebox">
<p>Jede Funktion hat eine <code>prototype</code>-Eigenschaft, in der das prototypische Objekt steckt. Erweitern Sie dieses, so vererben sich dessen Eigenschaften auf alle Instanzen, die mit der Funktion erzeugt werden.</p>
</div>
<p>In den obigen Beispielen haben wir dem neuen Objekt direkt im Konstruktor Eigenschaften und Methoden hinzugefügt. Das bedeutet, dass diese Objekte mit jeder Instanz neu angelegt werden. Das ist <em>eine</em> Möglichkeit, wie dem Objekt Funktionalität hinzugefügt werden kann. Sie ist unter anderem dann notwendig, wenn Konstruktor-Parameter an das Instanzobjekt kopiert werden, wie es im Beispiel mit <code>this.name = name;</code> getan wird.</p>
<p>Mit einer Funktion ist immer ein <strong>prototypisches Objekt (Prototyp)</strong> verknüpft. Es handelt sich um ein gewöhnliches allgemeines JavaScript-Objekt, wie wir es auch mit <code>new Object()</code> oder dem <code>Object</code>-Literal <code>{}</code> anlegen können. Dieser Prototyp ist bei eigenen Funktionen anfangs leer.</p>
<p>Über die Eigenschaft <code>prototype</code> können wir ausgehend vom Funktionsobjekt auf den Prototypen zugreifen. Dieses Objekt können wir entweder erweitern oder mit einem eigenen Objekt ersetzen. Im folgenden Beispiel wird der Prototyp erweitert, indem eine Methode hinzugefügt wird:</p>
<pre>
function Katze() {}
<strong>Katze.prototype.miau</strong> = function () {
alert("Miau!");
};
var maunzi = new Katze();
mauzi.miau();
</pre>
<p>Hier wird ein Funktionausdruck notiert und das Funktionsobjekt in <code>Katze.prototype.miau</code> gespeichert. Beim Prototypen wird also eine Eigenschaft namens <code>miau</code> angelegt. Darin steckt nun die neu angelegte Funktion. Wenn wir eine <code>Katze</code>-Instanz erzeugen, so besitzt sie eine <code>miau</code>-Methode.</p>
</div>
</div>
<div class="section" id="prototyp-vererbung">
<h2>Vererbung vom Prototypen zur Instanz</h2>
<div class="sidebox">
<p>Der Prototyp steht »hinter« einem Objekt: Wird bei dem Objekt eine Eigenschaft nicht direkt gefunden, so kann der Prototyp einspringen und sie bereitstellen.</p>
</div>
<p>Durch den Aufruf von <code>new Katze</code> wird wie gesagt intern ein zunächst leeres <code>Object</code> erzeugt. Der Prototyp des Konstruktors, <code>Katze.prototype</code>, wird dabei in die sogenannte <strong><dfn>Prototyp-Kette</dfn></strong> (<dfn>Prototype Chain</dfn>) des Objekts eingehängt. Dies ist eine geordnete Liste mit Objekten, die abgearbeitet wird, wenn auf eine Eigenschaft des Objekts zugegriffen wird.</p>
<p>Ein konkretes Beispiel: Wenn wir den Ausdruck <code>maunzi.miau</code> schreiben, dann arbeitet der JavaScript-Interpreter die Prototyp-Kette ab, um die Eigenschaft namens <code>miau</code> zu finden und damit den Ausdruck aufzulösen. Die Prototyp-Kette von <code>maunzi</code> hat folgende Einträge:</p>
<ol>
<li><code>maunzi</code> – das Instanzobjekt selbst</li>
<li><code>Katze.prototype</code> – der Prototyp für alle Objekte, die mit dem <code>Katze</code>-Konstruktor erzeugt wurden</li>
<li><code>Object.prototype</code> – der Prototyp, der hinter allen Objekten steht</li>
</ol>
<p>Das folgende Diagramm zeigt Objekte der Prototyp-Kette und listet deren Eigenschaften auf:</p>
<div class="js-objects">
<div class="object">
<h3>maunzi</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Katze.prototype</td></tr>
</table>
</div>
<div class="object">
<h3>Katze.prototype</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Object.prototype</td></tr>
<tr class="own"><th>miau</th><td>function () { alert("Miau!"); }</td></tr>
<tr class="dontenum"><th>constructor</th><td>Katze</td></tr>
</table>
</div>
<div class="object">
<h3>Object.prototype</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>null</td></tr>
<tr class="dontenum"><th>constructor</th><td>Object</td></tr>
<tr class="dontenum"><th>toString</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>toLocaleString</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>valueOf</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>hasOwnProperty</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>isPrototypeOf</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>propertyIsEnumerable</th><td>[native Funktion]</td></tr>
</table>
</div>
</div><!-- /.js-objects -->
<p>Wenn wir <code>maunzi.miau</code> notieren, dann wird nacheinander an diesen drei Objekten nach der Eigenschaft <code>miau</code> gesucht.</p>
<p>Das Instanzobjekt <code>maunzi</code> besitzt in diesem Beispiel keine sichtbaren Eigenschaften. Intern besitzt <code>maunzi</code> jedoch einen Verweis auf seinen Prototyp, das ist <code>Katze.prototype</code>. Dieser Verweis wird in der unsichtbaren Eigenschaft <code>[[Prototype]]</code> gespeichert und ist in einigen JavaScript-Engines über die besondere Eigenschaft <code>__proto__</code> les- und schreibbar.</p>
<p>
Das Objekt <code>maunzi</code> besitzt also keine Eigenschaft namens <code>miau</code>. Daher folgt der JavaScript-Interpreter dem Verweis zu Prototypen von <code>maunzi</code>: <code>Katze.prototype</code>. Dort wird er fündig, denn dort existiert eine Eigenschaft namens <code>miau</code>. Dies ist eine Funktion und sie kann mit dem Aufruf-Operator <code>(…)</code> ausgeführt werden: <code>mauzi.miau()</code>.</p>
<div class="sidebox">
<p>Prototypische Vererbung ist das Delegieren von Funktionalität von einem Objekt zum anderen. Das ist nicht an Konstruktoren und Instanzen gebunden. Wir nutzen diese lediglich, um den Prototyp-Verweis anzulegen.</p>
</div>
<p>Auf diese Weise stehen alle Eigenschaften eines Prototypen, im Beispiel <code>Katze.prototype</code>, auch beim Instanzobjekt zur Verfügung, im Beispiel <code>maunzi</code>. Dies ist das ganze Geheimnis hinter der <strong>prototypischen Vererbung</strong>. Wenn bei einem Objekt selbst die angeforderten Eigenschaften nicht gefunden wurde, so dienen die Objekte in der Prototyp-Kette als Fallback. Man spricht von einer <dfn>Delegation</dfn> (Übertragung, Weitergabe). Das Objekt gibt die Anfrage an seine Prototypen weiter.</p>
<p>Prototypische Vererbung funktioniert grundlegend anders als klassenbasierte Vererbung, denn JavaScript ist eine äußerst dynamische Sprache. Es gibt keine Klassen, die einmal deklariert werden und nach der Kompilierung unveränderlich sind. Über die Prototyp-Kette erben gewöhnliche Objekte von gewöhnlichen Objekten. Jedes Objekt kann der Prototyp eines anderen Objekts werden und »einspringen«, wenn es die angeforderte Eigenschaft nicht selbst bereitstellen kann.</p>
<p>Alle beteiligten Objekte einschließlich der Prototypen können zur Laufzeit beliebig verändert werden. Ein Prototyp ist also im Gegensatz zu einer Klasse kein fester Bauplan für immer gleiche Instanzen, sondern selbst ein beliebiges Objekt, an das Eigenschaftsanfragen delegiert werden. Deshalb ist der Begriff »Instanz« für das Objekt, welches <code>new Katze</code> erzeugt, letztlich irreführend. Wo es keine Klassen als rein abstrakten Bauplan gibt, sondern bloß flexible Objekte mittels Konstruktoren erzeugt werden und von Prototypen erben, so trifft dieser Begriff die Sache nicht. Er wird hier trotzdem der Einfachheit halber verwendet.</p>
</div>
<div class="section" id="prototypen-verstehen">
<h2>Prototypen verstehen: Die Glasplatten-Metapher</h2>
<p>Eine hervorragende Veranschaulichung von Prototypen hat Robin Debreuil ausgearbeitet. Er schrieb 2001 ein Tutorial über <a href="http://www.debreuil.com/docs/ch01_Intro.htm">objektorientierte Programmierung mit ActionScript 1.0 in Flash 5</a>. ActionScript war damals eine Sprache, die auf ECMAScript 3 aufbaute und damit in den Grundzügen mit JavaScript identisch war. Die Sprache ActionScript ist mittlerweile von prototypenbasierter auf klassenbasierte Objektorientierung umgestiegen – für JavaScript sind diese Erklärungen aber immer noch gültig und aufschlussreich.</p>
<p>Die Metapher beschreibt Objekte in der Prototyp-Kette als beklebte Glasplatten. Auf jeder Glasplatte sind farbige Zettel an bestimmten Positionen aufgeklebt. Diese Zettel entsprechen den Objekteigenschaften, die Positionen entsprechen Eigenschaftsnamen. Die Instanz selbst verfügt über ein paar Zettel, sein Prototyp und dessen Prototyp über weitere. Diese können auch an denselben Stellen kleben.</p>
<p>Die Funktionalität, über die die Instanz verfügt, ist nun eine Summe der Zettel der drei Glasplatten: In der Metapher werden die Glasplatten übereinandergelegt. Da sie durchsichtig sind, schimmern durch die Lücken die Zettel auf den darunterliegenden Platten durch. Das Gesamtbild, das sich so ergibt, setzt sich aus den Zetteln der drei Platten zusammen.</p>
<p>
<img src="images/prototypen-optimiert.svg" width="100%">
</p>
<p>Das Objekt ist demnach ein Mosaik, das sich aus den eigenen sowie fremden Eigenschaften zusammensetzt. Welches Objekt in der Prototyp-Kette nun eine gesuchte Eigenschaft bietet, ist unwichtig. An der gesuchten Stelle auf dem Glas ist ein Zettel sichtbar – in der Grafik z.B. oben links, des entspricht einem Eigenschaftsnamen, z.B. <code>eins</code>.</p>
<p>Die Metapher zeigt auch, dass Objekte in der Prototyp-Kette gleiche Eigenschaften bieten können. An der Stelle oben in der Mitte klebt ein gelber Zettel auf der Instanz-Glasplatte, aber auch ein orangener auf der, die <code>Konstruktor.prototype</code> darstellt. Beim Übereinanderlegen ist nur der gelbe Zettel der oben liegenden Instanz-Glasplatte sichtbar, der orangene wird überdeckt. Übertragen heißt das: Eine Eigenschaft kann am Instanzobjekt definiert werden, und schon überschreibt sie eine gleichnamige Eigenschaft am <code>prototype</code>-Objekt des Konstruktors.</p>
<div class="sidebox">
<p>Bei prototypischer Vererbung werden nur sich unterscheidende Eigenschaften beim abgeleiteten Objekt gesetzt.</p>
</div>
<p>Diese Art der Vererbung nennt man <dfn>Differential Inheritance</dfn>. Im Gegensatz zur klassenbasierten Vererbung werden beim abgeleiteten Objekt (der Instanz) keine Eigenschaften erzeugt. Die Instanz ist keine Kopie des Prototypen, die Instanz kann sogar leer sein, wie es Katzen-Beispiel der Fall ist. Erst wenn sich die Instanz vom Prototypen unterscheidet, wird bei der Instanz eine Eigenschaft angelegt, die einen anderen Wert als die des Prototypen besitzt. In der Glasplatten-Metapher bedeutet dies: An den Stellen, in denen die Instanz dem Prototyp gleicht, ist die Platte durchsichtig – sie delegiert . Wo sie sich unterscheidet, besitzt sie einen eigenen, andersfarbigen Zettel.</p>
<p>Nehmen wir an, dass hiesige Katzen meistens von der Rasse »Europäisch Kurzhaar« sind. Anstatt dies jedes Mal beim Erzeugen anzugeben, legen wir die Eigenschaft beim Prototypen an:</p>
<pre>
function Katze() {}
Katze.prototype.rasse = "Europäisch Kurzhaar";
var maunzi = new Katze();
alert(maunzi.rasse);
</pre>
<p>Greifen wir auf <code>maunzi.rasse</code> zu, so wird die <code>rasse</code>-Eigenschaft nicht bei der Instanz selbst, aber beim Prototypen gefunden. Denn die relevanten Objekte sehen so aus (<code>Object.prototype</code> wird ausgeblendet):</p>
<div class="js-objects">
<div class="object">
<h3>maunzi</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Katze.prototype</td></tr>
</table>
</div>
<div class="object">
<h3>Katze.prototype</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Object.prototype</td></tr>
<tr class="own"><th>rasse</th><td>"Europäisch Kurzhaar"</td></tr>
<tr class="dontenum"><th>constructor</th><td>Katze</td></tr>
</table>
</div>
</div><!-- /.js-objects -->
<p>Wenn wir nun eine Katze mit abweichender Rasse erzeugen wollen, so legen wir eine gleichnamige Eigenschaft bei der Instanz an, die die Eigenschaft des Prototypen verdeckt:</p>
<pre>
var maunzi = new Katze();
alert(maunzi.rasse); // Derzeit noch »Europäisch Kurzhaar« - vererbt vom Prototypen
maunzi.rasse = "Perser";
alert(maunzi.rasse); // Jetzt »Perser« - eigene Eigenschaft
</pre>
<p>Daraufhin besitzt die Instanz eine eigene, abweichende Eigenschaft:</p>
<div class="js-objects">
<div class="object">
<h3>maunzi</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Katze.prototype</td></tr>
<tr class="own"><th>rasse</th><td>"Perser"</td></tr>
</table>
</div>
<div class="object">
<h3>Katze.prototype</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Object.prototype</td></tr>
<tr class="own"><th>rasse</th><td>"Europäisch Kurzhaar"</td></tr>
<tr class="dontenum"><th>constructor</th><td>Katze</td></tr>
</table>
</div>
</div><!-- /.js-objects -->
</div>
<div class="section" id="private-objekte">
<h2>Private Objekte anstatt private Eigenschaften</h2>
<p>Viele klassenbasierte Sprachen erlauben es, die Sichtbarkeit von Instanzeigenschaften festzulegen und unterscheiden beispielsweise zwischen öffentlichen und privaten Eigenschaften.</p>
<p>In JavaScript (ECMAScript 3) gibt es keine Möglichkeit, gewisse Eigenschaften eines Objekts als privat zu deklarieren, sodass sie ausschließlich in Methoden des Objekt zur Verfügung stehen. Sobald wir einem Objekt eine Eigenschaft hinzufügen, ist diese auch überall dort verfügbar, wo das Objekt verfügbar ist.</p>
<p>Das folgende Beispiel zeigt, wie ein Instanzobjekt und auch der Prototyp nachträglich verändert werden können:</p>
<pre>
function Katze() {}
Katze.prototype.pfoten = 4;
Katze.prototype.miau = function () {
alert("Miau!");
};
var maunzi = new Katze();
var schnucki = new Katze();
// Erzeuge eine gleichnamige Eigenschaft bei der Instanz,
// um die vom Prototyp vererbte Eigenschaft zu überdecken:
maunzi.pfoten = 5;
alert('Maunzi hat nun ' + maunzi.pfoten + ' Pfoten.');
// Überschreibe Methode des Prototyps:
Katze.prototype.miau = function () {
alert("Wau, wau!");
};
schnucki.miau();
</pre>
<p>Plötzlich hat Maunzi fünf Pfoten und alle Katzen sagen »wau« anstatt »miau«. Dies ist sowohl ein Defizit von ECMAScript 3 als auch eine Stärke: Prototypen sind nicht abgeschlossen und Objekte immer erweiterbar. Erst ab ECMAScript 5 is es möglich, eine Eigenschaft als nicht überschreibbar und ein Objekt als nicht erweiterbar zu deklarieren.</p>
<div class="sidebox">
<p>Private Eigenschaften gibt es nicht – wir können stattdessen einen Funktions-Scope definieren, dessen private Variablen die Instanzmethoden einschließen.</p>
</div>
<p>Wie gesagt gibt es keine privaten Eigenschaften im Wortsinn, auch wenn manche diesen Begriff auch auf JavaScript anwenden. Denn wenn ein Objekt an der Instanz hängt, ist es in ECMAScript 3 auch notwendig nach außen sichtbar und unkontrolliert überschreibbar.</p>
<p>Wir können jedoch einen Trick anwenden, den wir bereits vom <a href="organisation-module.html#revealing-module">Revealing Module Pattern</a> kennen: In einem Funktions-Scope notieren wir lokale Variablen und zudem die öffentlichen Methoden des Objekts. Die öffentlichen Methoden haben auf erstere Zugriff, weil sie im selben Scope erzeugt wurden und damit <a href="organisation-verfuegbarkeit.html#closures">Closures</a> sind. Daher handelt es sich um sogenannte <dfn>privilegierte Methoden</dfn>.</p>
<p>Da der Konstruktor bereits einen Funktions-Scope bereitstellt, nutzen wir kurzerhand diesen für private Objekte. Damit die Methoden auf die privaten Objekte Zugriff haben, müssen sie im Konstruktor erzeugt und dürfen nicht über den Prototyp definiert werden. Über <code>this</code> werden sie ans Instanzobjekt gehängt.</p>
<pre>
function Katze(name) {
// --- Private Objekte ---
// Private Variablen
var pfoten = 4;
var gestreichelt = 0;
// Der Parameter »name« ist ebenfalls privat
// Private Funktionen
function miau() {
alert(name + ' macht miau!');
}
// --- Öffentliche (privilegierte) Eigenschaften ---
this.name = name;
// Öffentliche Methoden
this.kitzeln = function () {
alert(name + ' hat ' + pfoten + ' kitzlige Pfoten.');
miau();
};
this.streicheln = function () {
gestreichelt++;
miau();
};
}
var maunzi = new Katze('Maunzi');
maunzi.kitzeln();
maunzi.streicheln();
alert('maunzi.name: ' + maunzi.name);
// pfoten ist keine Objekt-Eigenschaft, also von außen unzugänglich:
alert('maunzi.pfoten: ' + maunzi.pfoten);
</pre>
<p>Der Konstruktor nimmt hier den Parameter <code>name</code> entgegen. Dieser ist automatisch eine lokale Variable. Zusätzlich werden zwei lokalen Variablen (<code>pfoten</code>, <code>gestreichelt</code>) sowie eine lokale Funktion (<code>miau</code>) angelegt. Der leeren Instanz werden eine Eigenschaft (<code>name</code>) und zwei öffentliche Methoden (<code>kitzeln</code>, <code>streicheln</code>) angehängt, die als verschachtelte Funktionsausdrücke notiert werden.</p>
<p>Nach dem Anlegen einer <code>Katze</code> mit dem Namen <var>Maunzi</var> werden die beiden Methoden aufgerufen. Sie haben Lese- und Schreibzugriff auf die privaten Objekte und können auch die private Funktion ausführen, welche von außen nicht zugänglich sind.</p>
<div class="sidebox">
<p>Funktions-Scopes und Closures sind der Schlüssel zu privaten Objekten, sowohl bei Konstruktoren/Instanzen und dem Revealing Module Pattern.</p>
</div>
<p>Wie das Beispiel zeigt, können auch private Funktionen angelegt werden. Private Funktionen können zum Beispiel interne, in verschiedenen Methoden verwendete Helfer sein. Sie entsprechen <em>privaten Methoden</em> in klassenbasierten Sprachen. Dieser Begriff ist auf JavaScript nicht anwendbar, da es sich eben nicht um Methoden des Instanzobjekt handelt. Sie sind lediglich in den tatsächlichen Instanzmethoden verfügbar, da diese Zugriff auf die Variablen des Konstruktor-Scopes haben.</p>
</div>
<div class="section" id="private-objekte-nachteile">
<h2>Nachteile von privaten Objekten</h2>
<p>Der gravierende Unterschied zu den vorigen Beispielen ist, dass die Nutzung des Prototyps und damit die Vererbung wegfällt. Anstatt die Methoden einmal am Prototyp zu erzeugen, werden sie bei jeder Instanz im Konstruktor von neuem angelegt. Heraus kommt folgende Prototyp-Kette:</p>
<div class="js-objects">
<div class="object">
<h3 class="special">maunzi</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Katze.prototype</td></tr>
<tr class="own"><th>name</th><td>'Maunzi'</td></tr>
<tr class="own"><th>kitzeln</th><td>function () { … }</td></tr>
<tr class="own"><th>streicheln</th><td>function () { … }</td></tr>
</table>
</div>
<div class="object">
<h3 class="special">Katze.prototype</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>Object.prototype</td></tr>
<tr class="dontenum"><th>constructor</th><td>Katze</td></tr>
</table>
</div>
<div class="object">
<h3 class="special">Object.prototype</h3>
<table>
<tr class="internal"><th>[[Prototype]]</th><td>null</td></tr>
<tr class="dontenum"><th>constructor</th><td>Object</td></tr>
<tr class="dontenum"><th>toString</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>toLocaleString</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>valueOf</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>hasOwnProperty</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>isPrototypeOf</th><td>[native Funktion]</td></tr>
<tr class="dontenum"><th>propertyIsEnumerable</th><td>[native Funktion]</td></tr>
</table>
</div>
</div><!-- /.js-objects -->
<p>Der Prototyp <code>Katze.prototype</code> ist also leer, die Eigenschaften hängen direkt an der Instanz und werden nicht vererbt. Dies hat verschiedene Konsequenzen:</p>
<div class="sidebox">
<p>Wenn Methoden über den Prototyp anstatt im Konstruktor erzeugt werden, wird Speicher eingespart. Dafür ist das Auflösen von Eigenschaften über die Prototyp-Kette etwas langsamer, da an mehreren Objekten gesucht wird.</p>
</div>
<p>Ein Überschreiben oder Löschen dieser Eigenschaften über den Prototyp ist nicht möglich. Ein nachträgliches Erweitern des Prototyps ist möglich. Solche Methoden sind allerdings nicht privilegiert, haben also keinen Zugriff auf private Objekte. Denn sie wurden nicht im Konstruktor definiert und schließen die privaten Objekte nicht ein. Aus demselben Grund ist kein nachträgliches Hinzufügen von privaten Objekten möglich.</p>
<p>Mit jedem Erzeugen einer <code>Katze</code>-Instanz werden alle Eigenschaften von neuem erzeugt. Die Instanzen teilen sich ihre Eigenschaften nicht mit anderen <code>Katze</code>-Instanzen. Dies wirkt sich negativ auf die Performance aus: Das Anlegen der Eigenschaften kostet Zeit und Arbeitsspeicher. Werden z.B. zehn Katzen instantiiert, so werden 20 Funktionsobjekte erzeugt (zehn mal <code>kitzeln</code> und <code>streicheln</code>). Würden die Instanzen diese Methoden vom Prototyp erben, so müssten sie nur einmal am Prototyp erzeugt werden.</p>
</div>
<div class="section" id="kapselung">
<h2>Wozu Kapselung gut ist und wann sie nötig ist</h2>
<p>Kapselung in objektorientiertem JavaScript hat zwei Bedeutungen: Zum einen wird das Programm so strukturiert, dass es möglichst nicht mit anderen Scripten in Konflikt kommt. Zum anderen bedeutet Kapselung die Trennung zwischen öffentlichen und privaten Objekten. Auf die öffentlichen können andere Scripte von außen zugreifen, die privaten sind diesen verborgen.</p>
<p>Die erste Art der Kapselung ist in jedem Fall sinnvoll, denn alle Scripte teilen sich das globale <code>window</code>-Objekt und müssen dafür Sorge tragen, nicht die Objekte anderer Scripte zu überschreiben.</p>
<p>Eine effektive Kapselung der zweiten Art bringt jedoch Nachteile mit sich. Wann also brauchen wir sie und in welchem Umfang?</p>
<p>Das Konzept der Kapselung in der objektorientierten Programmierung hat den Zweck, zwischen einer öffentlichen Programmierschnittstelle (<abbr lang="en" title="Application Programming Interface">API</abbr>) und der internen Implementierung zu unterscheiden. Sie hilft in aller erster Linie dem Programmierer, gut strukturierte, wiederverwendbare Software zu schreiben.</p>
<p>Die öffentliche Schnittstelle, also die Menge an öffentlichen Methoden, wird dokumentiert und von anderen Programmierern angesteuert. Sie bleibt idealerweise über mehrere Programmversionen gleich. Sie kann natürlich erweitert und beizeiten durch eine leistungsfähigere und flexiblere Schnittstelle ergänzt werden.</p>
<p>Die privaten Objekte umfassen alle Variablen und Methoden, die nur intern Gebrauch finden und die tatsächliche Implementierung strukturieren. Ein bloßer Anwender des Scriptes muss diese nicht kennen. Von Version zu Version kann sich die interne Umsetzung und somit die privaten Objekte ändern, während die Funktionalität nach außen hin gleich bleibt.</p>
<p>Ein zweiter Grund für Kapselung ist die effektive Sicherheit: Ein fremdes Script soll die Interna nicht lesen und manipulieren können. Dies ist in anderen Sprachen sehr wichtig, um robuste Programme zu ermöglichen. Der Aspekt der tatsächlichen Unsichtbarkeit ist im Falle von JavaScript jedoch nachrangig:</p>
<ul>
<li>Wenn ein Script ein anderes manipulieren will, dann ist das auf die eine oder andere Art möglich, weil ECMAScript 3 so dynamisch ist. Erst ECMAScript 5 ermöglicht es, einzelne Objekteigenschaften oder ganze Objekte »einzufrieren« und vor Manipulation zu schützen.</li>
<li>Mit Tricks ist es bei einigen JavaScript-Interpretern möglich, auf einen privaten Funktions-Scope zuzugreifen.</li>
<li>Es kann durchaus von Vorteil sein, die Interna eines Scriptes lesen, abändern und erweitern zu können. Das gehört zu den Features von JavaScript.</li>
</ul>
<div class="section" id="pseudo-privat">
<h3>Pseudo-private Objekte</h3>
<p>Aus diesen Gründen kann es ausreichen, Kapselung lediglich als Strukturierungskonzept zu verstehen. Es reicht dann, mit <strong>pseudo-privaten Objekten</strong> zu arbeiten. Diese sind nicht effektiv vor dem Zugriff von außen geschützt, finden jedoch in der Regel nur intern Verwendung.</p>
<p>Ein einfacher <code>Object</code>-Literal bietet keine effektive Kapselung, d.h. alle Eigenschaften sind von außen sichtbar und änderbar. Kapselung als Konzept lässt sich damit trotzdem umsetzen, indem klar zwischen öffentlichen und (pseudo-)privaten Eigenschaften unterschieden wird. Eine Konvention ist etwa, die privaten Eigenschaften mit einem <code>_</code> (Unterstrich) beginnen zu lassen. Ein entsprechendes <a href="organisation-module.html#revealing-module">Modul</a> könnte folgendermaßen aussehen:</p>
<pre>
var Modul = {
// Öffentliche Eigenschaften
öffentlicheMethode : function () {
alert(this._privateMethode());
},
// Pseudo-private Eigenschaften
<strong>_privateEigenschaft</strong> : 1;
<strong>_privateMethode</strong> : function () {
this._privateEigenschaft++;
return this._privateEigenschaft;
}
};
Module.öffentlicheMethode();
</pre>
<p>Dasselbe bei einem Konstruktor mit Prototypen:</p>
<pre>
function Konstruktor() {}
// Öffentliche Eigenschaften
Konstruktor.prototype.öffentlicheMethode = function () {
alert(this._privateMethode());
};
// Pseudo-private Eigenschaften
Konstruktor.prototype.<strong>_privateEigenschaft</strong> = 1;
Konstruktor.prototype.<strong>_privateMethode</strong> = function () {
this._privateEigenschaft++;
return this._privateEigenschaft;
};
var instanz = new Konstruktor();
instanz.öffentlicheMethode();
</pre>
<p>Technisch gesehen handelt es sich bei diesen pseudo-privaten Eigenschaften um ganz normale Eigenschaften, die sich in puncto Sichtbarkeit und Schreibbarkeit nicht von den sogenannten öffentlichen unterscheiden. Der Unterstrich im Namen hat keine Bedeutung für den JavaScript-Interpreter, er ist lediglich eine Namenskonvention.</p>
</div>
<div class="references">
<h3>Weiterführende Lektüre</h3>
<ul>
<li><a href="http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/#encapsulation"> Dmitry Soshnikov: ECMA-262-3 in detail. Chapter 7.1. OOP: The general theory.</a></li>
<li><a href="http://snook.ca/archives/javascript/no-love-for-module-pattern">Jonathan Snook: Why I don't love JavaScript's Module Pattern</a></li>
</ul>
</div>
</div>
<!--
<div class="functional-inheritance">
<h2>JavaScript kann mehr</h2>
<p>TODO: Factory-Methode und <code>Object.create</code> statt <code>new</code>, funktionale Vererbung</p>
</div>
<div class="section" id="mehrfachvererbung">
<h2>Mehrfachvererbung über die Prototyp-Kette</h2>
<p>TODO</p>
</div>
-->
<div class="sequence-navigation">
<p class="next"><a href="organisation-verfuegbarkeit.html" rel="next">Organisation von JavaScripten: Objektverfügbarkeit und this-Kontext</a></p>
<p class="prev"><a href="organisation-module.html" rel="prev">Organisation von JavaScripten: Module und Kapselung</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>