Hier wird nicht versucht, ECMAScript vollständig zu beschreiben ( schwache Typisierung von Datentypen, dynamische Objekte, Lambda, ausdrucksstarke literale Objektnotation, funktioninterner Geltungsbereich von Variablen, Closure, automatisches Speichermanagement, usw. ), sondern eher jene ausdruckstarke Teilmenge herausgestellt, die zu zuverlässigen, robusten Programmen führt. In gewisser Weise ist ECMAScript ein "Lisp im C-Gewand".
Ein Anwendungsprogramm (kurz Anwendung = Applikation, engl. application software) braucht ein Betriebssystem und eine geeignete Laufzeitumgebung. Etwa so wie abstrakte Wissenschaftstheorien eine Historie und ein geistig-abstraktes Bezugssystem haben, hängen auch Software-Theorien und umfangreiche Projekt-Entwürfe und -Entwicklungen vom konkreten Kontext ab. Zu Entwurfsmustern (Design Patterns) gehören die Kriterien des Zwecks (purpose) und des Bereichs, auf den sie wirken (scope). Es gibt z.B.
Erzeugungsmuster (Creational Pattern) a) Klassenmuster: Fabrikmethode (Factory Method, Virtual Constructor) b) Objektmuster: Abstrakte Fabrik (Abstract Factory, Kit) Erbauer (Builder) Prototyp (Prototype) Einzelstück (Singleton)
Strukturmuster (Structural Pattern) a) Klassenmuster: Adapter (Adapter, Wrapper) (Adapter mit Vererbung oder Klassenadapter) b) Objektmuster: Adapter (Adapter, Wrapper) (Adapter mit Assoziation oder Objektadapter)
Brücke (Bridge, Handle/Body) Kompositum (Composite) Decorator (Wrapper) Fassade (Facade) Fliegengewicht (Flyweight) Stellvertreter (Proxy, Surrogate)
Verhaltensmuster (Behavioral Pattern) a) Klassenmuster: Interpreter (Interpreter), Schablonenmethode (Template Method) b) Objektmuster: Zuständigkeitskette (Chain of Responsibility), Kommando (Befehl, Command, Action, Transaction), Iterator (Iterator, Cursor), Vermittler (Mediator), Memento (Memento, Token), Beobachter (Observer, Dependents, Publish-Subscribe, Listener), Zustand (State, Objects for States), Strategie (Strategy, Policy), Besucher (Visitor)
Die Beschreibung eines Entwurfsmusters durch die "Gang of Four" folgt dem folgenden Schema:
Brendan Eich (1964- ) entwickelte LiveScript (Vorläufer von JavaScript). Biografie von Brendan Eich etwa: 7 Jahre bei Firma Silicon Graphics, 3 Jahre bei MicroUnity Systems Engineering, (Betriebssystemkerne, DSP, Portierung des C-Compilers gcc für den MIPS-R4000), ab 1995 Arbeiten an Mocha-LiveScript-JavaScript und Netscape Navigatorbei Netscape Communications, anfang 1998 half Brendan Eich bei der Gründung von mozilla.org, seit 2005 Chief Technical Officer der Mozilla Corporation (siehe z.B. Brendan Eich )
Sprachen haben starke und schwache Ausdrucksformen. Viele Features hat JavaScript von anderen Sprachen übernommen. Die Syntax ist an Java angelehnt, die Funktionen stammen von Scheme und die prototypische Vererbung von Self. Die regulären Ausdrücke wurden von Perl übernommen
Douglas Crockford: "JavaScript kam aus dem Nichts und breitete sich in alarmieren kurzer Zeit weltweit aus. Es gab nie eine Laborphase, in der sie ausprobiert und aufpoliert werden konnte. Sie wurde, so wie sie war, direkt in Netscape Navigator 2 integriert und war zu diesem Zeitpunkt nicht sonderlich ausgereift. Als Java®-Applets versagten, wurde JavaScript zur "Sprache des Web". Die Popularität von JavaScript hat nahezu nichts mit seiner Qualität als Programmiersprache zu tun."
Typisierung (engl. typing) ist stets ein Kompromiss zwischen möglichst strikten statischen Prüfungen und einer die Flexibilität und Ausdrucksstärke der Sprache bewahrenden Sprachform. Es gibt starke Typisierung (strong typing), schwache Typisierung (weak typing), dynamische Typisierung (dynamic typing), statische Typisierung (static typing), explizite Typisierung (explicit typing) und implizite Typisierung (implicit typing). Die Typisierung hängt mit zahlreichen weiteren Aspekte (Polymorphe Werte, Typumwandlung, Russellschen Antinomie, usw.) zusammen.
Bei der Programmierung dient Typisierung dazu, eine formal-korrekte Verwendung von Objekten der Programmiersprachen (wie z. B. Variablen, Funktionen, Objekte) zu erzwingen, um dadurch möglichst viele Laufzeitfehler zu vermeiden.
Eine starke, statische Typisierung (strong typing wie z.B. bei Java) kann zahlreiche Fehler bereits beim Compilieren aufdecken. Dennoch werden Test's gebraucht, um mögliche andere, wichtige Fehler und deren Ursachen zu finden. ( siehe z.B. Typisierung )
Im Gegensatz zur starken, statischen Typisierung verzichtet die dynamische, schwache Typisierung (z.B. JavaScript, Python und Ruby) auf eine explizite Typisierung. Die Zuteilung des Typs einer Variablen erfolgt zur Laufzeit eines Programms. Der Typ einer Variablen ergibt sich aus dem Typ des Wertes, der ihr zugewiesen wird. (siehe z.B. Dynamische Typisierung ).
Zu ECMAScript gehört eine dynamische, schwache Typisierung von Datentypen (weak typinges, es gibt kein Typecasting), die für die Programmentwicklung befreiend wirken kann.
Einfache Datentypen sind Zahlen (Number), Zeichenkette (String), Boolsche Werte (Boolean, true, false), null, undefined. Diese können Methoden besitzen, sind aber unveränderlich.
Objekte sind bei ECMAScript veränderbare, über Schlüssel gebundene Sammlungen ( Array, Function, RegEx, Object ).
Ein Objekt ist ein Container mit Eigenschaften, wobei eine Eigenschaft einen Namen (bel. String, auch leerer String) und einen Wert (bel., außer undefined) besitzt. Objekte sind klassenfrei. Objekte sind nützlich, um Daten zu sammeln, zu strukturieren. Objekte können andere Objekte enhalten.
John Lennon: And you think you're so clever and classless and free
ECMAScript ist eine prototypische Sprache und Objekte können direkt von anderen Objekten erben. Bei ECMAScript sind Funktionen Objekte. Objekte sind Sammlungen von (Namen/Wert)-Paaren. Implizite Deklaration werden oft als Prototyp bezeichnet. Wird z.B. ein ECMAScript Funktionsobjekt angelegt, so wird bei der Funktionserzeugung ein Funktions-Konstruktor-Code, etwa this.prototype = {construktor: this}; ausgeführt. Das prototyp-Objekt ist der Ort, wo ererbte Elemente abzulegen sind.
Bei der Programmentwicklung wird versucht, eine Reihe von Anforderungen an die Applikation auf Methoden (Funktionen) und Datenstrukturen abzubilden. Funktionen fassen eine Reihe von Anweisungen zusammen. Eine Funktion kann wieder verwendet werden. Der Funktionsrumpf bildet den Sichtbarkeitsbereich für die Funktionsparameter und für weitere funktionsinterne Variablen, Objekte, Funktionen.
Die aus Object-Literalen erzeugten Objekte sind mit Object.prototypeverknüpft (prototypische Delegation). Die aus Function-Literalen erzeugten Funktionen sind mit Function.prototype verknüpft (das selbts wieder mit Object.prototype verknüpft ist). Zu jeder Funktion gehört als Wert von Function.prototype eine construktor-Eigenschaft.
Ein vereinfachter Funktion-Ausdrucke sieht etwa so aus: function foo() {}. Um zu verdeutlichen, daß Funktionen Werte sind, ist es günstiger, anstelle eines Funktion-Ausdruckes die folgende Funktion-Anweisung var foo = ...zu verwenden, die der Variablen foo als Wert das "Funktionsliteral" zuordnet.
Funktionen werden in ihrem Geltungsbereich "Hochgezogen", dadurch wird die Vorschrift gelockert, daß Funktionen vor der Verwendung definiert sein müssen.
Zu einem ECMAScript-Code gibt es einen Ausführungskontext.
Code wird in einem zugehörigen "execution context" ausgewertet.
Jeder Funktionsaufruf wird in dem Ausführungskontext der aufgerufenen Funktion ausgeführt.
Zu einem Ausführungskontext gehören
Variablenobjekt {Variablen, Funktionsdeklaration, Parameter, ...},
Scope-Chain [Variablenobjekt + alle übergeordneten Scopes],
this-Wert gebunden an das Kontextobjekt,
weitere Zustände sind optional.
Globaler Ausführungskontext:
var x = 10; function fn() {} // Funktionsdeklaration (wird gespeichert) (function fa() {}); // Funktionsausdruck (wird nicht gespeichert) // (this.x == x) ist true // (window.fn == fn) ist true // fa is nicht definiert
Das folgende Beispiel (nach Dmitry A. Soshnikov) soll so nicht verwendet werden und dient lediglich dem Verständnis und der Verdeutlichung des verborgenen __proto__-"Zeigers".
Bei einem Methoden-Aufruf im Objekt wird this an das Objekt {...} gebunden. Dies triff auf die folgende Funktion calculate zu.
var a = { x: 10, calculate: function (z) { return this.x + this.y + z } }; var b = { y: 20, __proto__: a }; // __proto__ verweis auf a var c = { y: 30, __proto__: a }; // __proto__ verweis auf a // Rufe die vererbte Methode auf b.calculate(30); // ergibt 60 c.calculate(40); // ergibt 80
arguments ist (leider nur) ein array-artiges Objekt, das die Parameterwerte enthält. arguments besitzt die length-Eigenschaft, aber alle Array-Methoden fehlen.
Beispiel: Es soll die Summe der Parameterwerte berechnet werden.
calc1 nimmt ungeprüft jedes arguments[i].
number und string können addiert werden, für einen
Array wird automatisch "toString()" duchgeführt.
calc2 verwendet von den arguments[i] nur die number
Ausnahmen (Exceptions) sind ungewöhnliche, aber nicht völlig unerwartete Pannen, die den normalen Progammfluß stören. Eine solche Panne soll bei calc3 eine Ausnahme auslösen. calc3 zeigt die Exception-Verwendung mit try, throw, catch.
Liefert die Ausgabe:
Für Array's gibt es bereits zahlreiche,eingebaute Methoden, wie .concat(), .join(), .pop(), .push(), .reverse(), .shift(), .slice(), .sort(), .splice(), .unshift().
Aktuell stehen bei den 'gängigen' Browsern ( Quelle: http://kangax.github.com/es5-compat-table/ ) auch .every, .some, .forEach, .map, .filter, .reduce, .reduceRight, .indexOf, .lastIndex 'nativ' zur Verfügung, die (falls erforderlich für IE < 9) auch per Array.prototype.every, Array.prototype.some, Array.prototype.forEach, Array.prototype.map, Array.prototype.filter, Array.prototype.reduce, Array.prototype.reduceRight, Array.prototype.indexOf, Array.prototype.lastIndex vom Entwickler definiert werden können.
Die vorhandene Methode .slice(), greift einen Bereich von Elementen heraus. Es gibt aber keine eingebaute Methode .del, die einen Bereich von Elementen löscht. Möglich ist die folgende Funktionsdefinition für del, bei der die ("umständliche") prototype-Schreibweise sichtbar ist.
Array.prototype.del = function(start, end) { var r = this.slice(0, start); if(!isNaN(end)) { r = r.concat(this.slice(end+1)); } return r; };
Günstiger ist die Nutzung der User-Function method, die dann für zahlreiche weitere Fälle zur Prototype-Funktionsdefinition verwendet werden kann.
Liefert die Ausgabe:
ECMAScript verfügt über ausdrucksstarke literale Objektnotation (Schlüssel/Werte-Paare)
this
wird zum Aufrufzeitpunkt an das Objekt gebunden.
Ein literales Objekt ist durch umschließende {...} gekennzeichnet. Ein literales Objekt kann überall dort stehen, wo ein Ausdruck steht. Objekte werden stets per Referenz übergeben. Es findet keine inhaltliches kopieren statt.
Liefert die Ausgabe:
In der Programmierung bedeutet Reflexion (engl. reflection) bzw. Introspektion, dass ein Programm seine eigene Struktur kennt und diese, wenn nötig, modifizieren kann. So kann die CPU den Opcode als Daten betrachten, eine virtuellen Maschine kann zur Laufzeit ergänzende Informationen abzufragen. Reflexion ermöglichen eine größere Laufzeitflexibilität, weil ergänzende Informationen dynamisch aufgerufen und ggf. Typen und Objekte sogar dynamisch strukturiert werden können. (siehe z.B. Reflexion ).
ECMAScript hat zur Laufzeit die .hasOwnProperty(), .instanceof, .propertyIsEnumerable() Abfragemöglichkeit. Die Typabfrage mit typeof kann zuverlässiger gemacht werden:
function type_of(v) { if (v === null) { return 'null'; } if(typeof v === 'undefined'){ return 'undefined'; } return Object.prototype.toString.call(v).slice(8,-1).toLowerCase(); }
Die Prototyp-Beziehung ist eine dynamische Beziehung. Wenn aus einem Objekt ein Eigenschaftswert abgerufen wird und dieser Eigenschaft nicht im Objekt existiert, so wird versucht, den Eigenschaftswert im Prototyp-Objekt ("Vorgänger-Objekt") abzurufen. Die ("zurückgehende") Prototyp-Verkettung endet bei Object.prototype. Ist die angeforderte Eigenschaft innerhalb der Prototyp-Kette nicht zu finden, so ist der Rückgabewert undefined.
Array und Object sind Objekte. Es gibt Ähnlichkeiten und Unterschiede. Array's werden verwendet, wenn die Eigenschaftsnamen sequenzielle Integerwerte sind (z.B. 0, 1, 2, ...) andernfalls Objektliterale. Array erbt die verfügbaren Methoden von Array.prototype. Für Array's gibt es bereits zahlreiche,eingebaute Methoden, wie .concat(), .join(), .pop(), .push(), .reverse(), .shift(), .slice(), .sort(), .splice(), .unshift().
Array hat die Eigenschaft .length, das dem (max. Index + 1) entspricht. arr.length kann (max. 4294967295) gesetzt werden, bei Vergrößerung wird nicht mehr Platz allokiert, bei Verkleinerung werden "überschüssige" Elemente gelöscht.
Object erbt die verfügbaren Methoden von Object.prototype.
Liefert die Ausgabe:
Prototyp-basierte Programmierung (klassenlose Objektorientierung) verzichtet auf das Sprachelement der Klasse (siehe z.B. Prototyp-basierte Programmierung Prototyp Entwurfsmuster ).
Für Vererbungsverfahren und das Hinterlegen von gemeinsame Eigenschaften verwendet ECMAScript eine endliche Prototyp-Kette (prototype chain). Nach dem ECMAScript-Standard ist jedes Objekt ist mit einem Prototyp-Objekt verknüpft, von dem es Eigenschaften erben kann. Alle mit Objektliteralen erzeugten Objekte sind mit Object.prototype verknüpft. Wiederverwendbarer Code wird über die Prototyp-Kette erreicht (Delegation basierte Vererbung, Prototyp-basierte Vererbung).
Eine prototyp-basierte Programmierung kommt ohne (Erzeuger-) Klassenhierarchie aus und erlaubt ein (schnelles) erzeugen komplexer Objekte, die Erzeugung und Einbindung von Unterklassen zur Laufzeit.
Objekte werden durch das Clonen bereits existierender Objekte erzeugt. Alle bestehenden Objekte können Prototypen neuer Objekte sein. Beim Clonen werden alle Eigenschaften (Attribute und Methoden) vom Prototyp-Objekt geerbt und durch eigene Eigenschaften erweitert
__prototype__ entspricht bei ECMAScript einem verborgenen Zeiger zum existierenden Objekt. Dadurch kann der Umfang von eingebauten, verfügbaren Methoden nachträglich erweitert werden.
ECMAScript erlaubt die Type-Erweiterung (prototype). Auch die grundlegenden Typen ( Function, Array, String, Number, Boolean, RegEx ) können mit Hilfe von prototype User-Methoden hinzu gefügt werden
ECMAScript kann zwar new Boolean(false), new Number, new String verwenden, was nicht empfehlenswert ist.
Der new-Operator erzeugt ein neues Objekt, das vom prototyp-Member des Operanden erbt und dann den Operanden aufruft, um das neue Objekt an this zu binden. Das gibt dem Operanden (der besser eine Konstruktorfunktion wäre) die Gelegenheit, das neue Objekt anzupassen, bevor es zurück gegeben wird. Wird aber der new-Operator vergessen, so entsteht ein einfacher Funktionsaufruf und this wird ohne Warnhinweis an das globale Objekt gebunden und nicht an das neue Objekt.
Douglas Crockford: "Vermeiden sie new Object und new Array. Verwenden sie statdessen { } und [ ]"
Liefert die Ausgabe:
Für Programmieranfänger von ECMAScript kann "new" seine Tücken haben. Objekte werden stets per Referenz übergeben. Es findet keine inhaltliches kopieren statt. Anstelle eines unübersichtlichen prototype ist es günstiger, eine "Factory-Methode" Object.create() zu verwenden. Dadurch wird das prototype und new in Object.create() versteckt. Dadurch vereinfachen sich Datenkapselung (Encapsulation, information hiding), Vererbung (Inheritance), object composition ("has a" relationship), Association ("sending a message", "invoking a method", "calling a member function"), Aggregation (object contains another object), Polymorphism (dynamische Types).
Liefert die Ausgabe:
Damit die Function isArray(x) auch für Array's funktioniert, die in einem anderen Fenster oder Frame definiert wurden, sind zahlreiche Abfragen erforderlich.
Liefert die Ausgabe:
Liefert die Ausgabe:
Der funktion-interne Geltungsbereich von Variablen bewirkt, daß
innere Funktionen Zugriff auf die Werte von Variablen
in der umschließenden, äußere Funktion
(außer this
, arguments
) haben.
Tennet Correspondence Principle (test of lambda abstraction)
Blocks (aber nicht für return, break, continue, this, arguments, var, function):
{…} ≡ (function(){…})();
Expressions (aber nicht für this, arguments:
(…) ≡ (function(){return …;})()
Hier ein erstes Beispiel, das die Sichtbarkeit der Variablen x zeigt.
Liefert die Ausgabe:
Anstatt obj
mit einem Objektliteral zu initialisieren, kann eine namenlose Funktion
aufgerufen werden, die ein Objektliteral zurück gibt (Funktions-Ausführung infolge von ()
).
Die Variable priv
ist für die public-Funktionen verfügbar,
aber der umschließende Funktions-Geltungsbereich sorgt dafür,
daß priv
vor dem Rest des Programmes verborgen bleibt.
// Beispiel: var s1 = 'hallo', s2 = 'welt', len1 = s1.length, n1 = obj.set(' '+s1).add(s2+' ').trim().get().length, n2 = obj.set(s1).add(obj.pub).add(s2) .del(len1,len1+obj.pub.length).get().length, txt = "n1 = " + n1 + "<br />n2 = " + n2; document.getElementById("entwicklungsmuster").innerHTML = txt; // Liefert die Ausgabe:
Liefert die Ausgabe: