JavaScript/Operatoren/new

Aus SELFHTML-Wiki
< JavaScript‎ | Operatoren(Weitergeleitet von New)
Wechseln zu: Navigation, Suche

Der Operator new ruft eine Funktion als Konstruktor auf. Dabei wird ein neues Objekt erzeugt, welches man als Instanz des Konstruktors bezeichnet. Unter Verwendung des Operators new können sowohl Instanzen eingebauter Konstruktoren wie beispielsweise Object, als auch Instanzen selbstdefinierter Konstruktoren erzeugt werden. Der Aufruf einer Funktion als Konstruktor stellt eine Möglichkeit dar, die Vererbung von Eigenschaften und Methoden zu implementieren, da der Prototyp eines auf diese Weise erzeugten Objektes standardmäßig das Objekt ist, welches in der Eigenschaft prototype des Konstruktors hinterlegt ist.


Syntax

new Constructor([arguments])


Beschreibung

Wird eine gewöhnliche Funktion definiert, entweder durch Deklaration oder über einen Ausdruck, dann wird hierdurch ein Funktionsobjekt erzeugt, welches über zwei interne Methoden für den Aufruf verfügt, die Methode [[Call]] für den normalen Funktionsaufruf und die Methode [[Construct]] für den Aufruf als Konstruktor.


Beispiel
'use strict';

function normal ( ) {
  console.log(typeof this);
}

const value = normal( ); // undefined

console.log(typeof value); // undefined


Im Beispiel oben wird zunächst eine gewöhnliche Funktion deklariert. Diese wird danach über ihren Bezeichner referenziert und durch die dahinter notierten runden Klammern, den sogenannten Call Operator, aufgerufen. Hierbei handelt es sich um einen normalen Funktionsaufruf über die interne Methode [[Call]]. Je nach Ausführungsmodus verweist die Kontextvariable this der Funktion bei dieser Form des Aufrufs auf das globale Objekt, oder sie besitzt den primitiven Wert undefined. Der Rückgabewert der Funktion ist bei einem normalen Funktionsaufruf, sofern nichts anderes bestimmt wurde, ebenfalls undefined.


Beispiel
'use strict';

function Constructor ( ) {
  console.log(typeof this);
}

const instance = new Constructor; // object

console.log(typeof instance); // object


In diesem Beispiel wird ebenfalls eine Funktion deklariert. Diese wird im Gegensatz zum letzten Beispiel jedoch unter Verwendung des Operators new als Konstruktor aufgerufen. Das heißt, der Aufruf erfolgt hier nicht über die interne Methode [[Call]], sondern über die Methode [[Construct]]. Infolgedessen wird ein neues Objekt erzeugt und dieses wird an die Kontextvariable this gebunden, weshalb als Ergebnis der Prüfung mit dem Operator typeof der String object ausgegeben wird. Dieses Objekt, die Instanz, ist darüber hinaus auch der Rückgabewert der Funktion.


Beispiel
function Constructor (value) {
  'use strict';
  this.name = value;
}

const instance = new Constructor('instance');

console.log(instance.name); // instance


Werden beim Aufruf einer Funktion als Konstruktor, so wie im letzten Beispiel, keine Argumente übergeben, dann können die runden Klammern nach dem Bezeichner der Funktion weggelassen werden, da der Aufruf selbst bereits Teil der Semantik des Operators new ist. Sollen hingegen Argumente an den Konstruktor übergeben werden, dann sind auch hier runde Klammern zu notieren, welche die Liste der Argumente umschließen, wie bei einem normalen Funktionsaufruf auch. Übergebene Werte können wie in dem Beispiel oben dazu verwendet werden, um Eigenschaften auf dem erzeugten Objekt zu definieren, welches über die Kontextvariable this referenziert wird.


Beispiel
const result = instance.hasOwnProperty('name');

console.log(result); // true


Hierbei ist zu beachten, dass Eigenschaften oder Methoden, die innerhalb des Konstruktors durch Zuweisung oder ausdrückliche Definition auf dem Objekt angelegt werden, welches über die Variable this referenziert wird, zu eigenen Eigenschaften oder Methoden dieses Objektes werden, weshalb die Überprüfung mit hasOwnProperty für die im letzten Beispiel direkt auf dem Objekt angelegte Eigenschaft name den booleschen Wert true zurückgibt.


Beispiel
function createObject (value) {
  return {
    name : value
  };
}

const object = createObject('object');

console.log(object.hasOwnProperty('name')); // true


Das letzte Codebeispiel demonstriert eine Objekt-Konstruktion mit einer einfachen Funktion – ganz ohne new und Konstruktorausruf.

Ein Gedankenspiel:
Die im Konstruktor (egal, ob mit new oder als "einfaches" return-Objekt) deklarierte Methoden werden immer mit dem gleichen Quelltext in jedes erzeugte Objekt geschrieben, obwohl mit this nur auf das eigene Objekt zugegriffen wird? Zehn neue Objekte = zehn Kopien des gleichen Codes?! Wäre es nicht eleganter und effizienter, die Methode nur einmal im Speicher abzulegen?

Genau das ist das Besondere an der new-Methode: Sie kann Methoden der Prototypen-Eigenschaft dem neuen Objekt vererben.


Beispiel
function Constructor(value) {
  'use strict';
  this.key = value;
}

Constructor.prototype.showString = function () { //Prototyp-Methode
  console.log(this.key);
}

var newObject = new Constructor("Hello!");
newObject.showString(); //Hello!


Die Funktion Constructor ist noch normal. showString() dagegen wird nicht direkt an Constructor gebunden, sondern an die Eigenschaft prototype. Das anschließende new instantiiert dann im ersten Schritt Constructor.prototype und führt im zweiten Schritt die Funktion Constructor() in diesem neuen Kontext aus. newObject.showString() führt dann eigentlich die Funktion Constructor.prototype.showString() ausgeführt, dabei zeigt this auf newObject und showString() gibt Eigenschaft newObject.key aus.

Werfen wir einen Blick auf die beteiligten:

  1. console.log(Constructor.showString);
    
    = undefined
    es gibt also keine Funktion Constructor.showString
  2. console.log(newObject.showString);
    
    = [Function]
    hinter newObject.showString verbirgt sich eigentlich die Funktion Constructor.prototype.showString(), nur ausgeführt im Kontext von newObject. this verweist newObject
    (Firefox 48 ist gesprächiger: function Constructor.prototype.showString())
  3. console.log(Constructor.prototype);
    
    = Constructor { showString: [Function] }
    zeigt den Objekt-Charakter der Prototypen-Eigenschaft.


Der Vorteil der Kombination von new und Prototypen liegt bei der zentralen Definition von Methoden und Eigenschaften im Prototypen und ihrer Vererbung über die Prototypenkette beim Konstruktoraufruf an die neue Instanz. Die Methoden und Eigenschaften sind an den Prototyp delegiert, müssen also nicht mehr auf jeder Instanz definiert werden.


Beispiel
function Constructor (value) {
  'use strict';
  this.name = value;
}

const prototype = Object.getPrototypeOf(new Constructor);

console.log(Constructor.prototype === prototype); // true


Die Methode getPrototypeOf gibt den Prototype eines Objektes aus, der in diesem Beispiel der Konstante prototype zugewiesen wird. Der anschließende Vergleich beweist, dass prototype eine neue Instanz von Constructor.prototype ist.


Beispiel
function Constructor ( ) {
  return {
    key : 'value',
  }
}

Constructor.prototype.methode = function (){
  return "Prototypen-Methode";
}

const instance = new Constructor;
const prototype = Object.getPrototypeOf(instance);

console.log(Constructor.prototype === prototype); // false


Rückgabewerte via return in der Konstruktorfunktion sind problematisch. Sind es Skalare (Boolean, String oder Number), verwendet new weiterhin den Prototypen und die neue Instanz zurück. Gibt der Konstruktor dagegen ein eigenes Objekt zurück, verwendet new dieses Rückgabeobjekt statt des eigentlich beabsichtigten Prototypen! Im Beispiel sind eine Konstruktorfunktion Constructor() mit eigenem Rückgabeobjekt und eine Prototypenmethode methode() definiert. new gibt dennoch das Objekt mit dem Wertepaar key: "value" zurück. Der Vergleich auf den Prototypen zeigt den Unterschied (=false).

Verschiedene Arten von Funktionen

Wie im letzten Abschnitt gesehen, besitzen alle gewöhnlichen Funktionen eine interne Methode [[Construct]], unabhängig davon, ob sie deklariert oder als Ausdruck notiert werden. Dem zur Folge können alle diese Funktionen mittels new als Konstruktor aufgerufen werden. Jedoch ist es so, dass nicht alle Funktionsobjekte über eine solche interne Methode verfügen.


Beispiel
const symbol = new Symbol('description'); // Type Error

console.log(symbol);


Zum Beispiel besitzen einige der eingebauten Standardobjekte, wie etwa das Funktionsobjekt Symbol, keine solche Methode für den Konstruktorenaufruf, weshalb der Versuch eines Aufrufs mittels new hier einen Typfehler produziert. Solche Funktionen können also nur über die interne Methode [[Call]] aufgerufen werden, die grundsätzlich jedes Funktionsobjekt besitzt.


Beispiel
const Constructor = value => {
  'use strict';
  this.key = value;
};

const instance = new Constructor('value'); // Type Error


Die in der sechsten Edition des Standards eingeführten Pfeilfunktionen können ebenfalls nicht als Konstruktor aufgerufen werden. Ein solcher Aufruf führt entsprechend auch hier zu einen Fehler. Bei diesen Funktionen ist darüber hinaus zu beachten, dass sie über keine eigene Bindung für die Kontextvariable this verfügen, das heißt, hier wird grundsätzlich der Wert von this der umgebenden lexikalischen Umgebung referenziert. Dem zur Folge hat die Anweisung, die Funktion im Strict Mode auszuführen, hier keinen Einfluss auf den Wert von this innerhalb der Funktion. Würde diese also über [[Call]] aufgerufen, dann würde hier eine globale Variable produziert, da der Ausführungskontext in dem die Funktion definiert wurde im normalen Modus ausgeführt wird und this hier entsprechend auf das globale Objekt verweist.


Beispiel
function * Generator ( ) {
  console.log(this);
  yield 'value';
}

const instance = new Generator; // Type Error


Auch Generatorfunktionen verfügen über keine interne Methode [[Construct]] und können dem zur Folge nicht als Konstruktor aufgerufen werden. Dies wäre hier allerdings auch nicht besonders sinnvoll, da diese Funktionen bereits bei einem normalen Funktionsaufruf über [[Call]] ein Objekt erzeugen und zurückgeben.


Beispiel
class CustomType {
  constructor ( ) {
    this.type = this.constructor.name;
  }
  logType ( ) {
    console.log(this.type);
  }
}

const instance = new CustomType;

instance.logType( ); // CustomType


Jedenfalls ist es nicht nur möglich, sondern auch zwingend erforderlich, eine Klasse als Konstruktor aufzurufen. Diese in der sechsten Edition des Standards eingeführten Funktionsobjekte besitzen also eine interne Methode [[Construct]]. Tatsächlich können Klassen auch nur über diese Methode aufgerufen werden, da hier anders als in den vorangegangenen Beispielen ein Aufruf über [[Call]] einen Fehler produziert.

Unbeabsichtigte Erzeugung globaler Variablen

Wenn Ihr Code nicht im Strict Mode läuft, wird beim gewöhnlichen Aufruf einer Funktion die Kontextvariable this auf das globale Objekt gesetzt &ndash in Browsern also das window-Objekt. Die Folgen können unangenehm sein, wenn Sie eine Konstruktorfunktion ohne new verwenden:

Beispiel
function Constructor ( ) {
  this.key = 12345;
}

const instance = Constructor( );

console.log(instance.key);    // undefined ?!
console.log(key);             // 12345 !?

Das fehlende new hat dazu geführt, dass die von Constructor gewünschte Initialisierung einer Objekteigenschaft statt dessen in den globalen Datenraum geschrieben hat. Besser wäre es gewesen, sie hätte einen Fehler ausgelöst.

Das ist einer der Gründe für den strikten Modus. Dort wäre this durch das vergessene new auf undefined initialisiert worden:

Beispiel
function Constructor ( ) {
  'use strict';                  // Strikten Modus erzwingen
  this.key = 'value';
}

const instance = Constructor( ); // Type Error: Cannot set properties of undefined
Empfehlung: Führen Sie Ihre Programme immer im strikten Modus aus. Wenn sich das nicht machen lässt, erzwingen Sie den strikten Modus in Konstruktorfunktionen oder prüfen Sie die Verwendung von new mittels new.target, wie im nächsten Abschnitt beschrieben.

new.target

Es gibt tatsächlich Funktionen, die abhängig von der Art ihres Aufrufs (mit oder ohne new) unterschiedliche Dinge tun. Beispiele dafür sind Number oder Boolean, die ohne new einfache Typkonvertierung treiben und mit new ein Wrapper-Objekt für einfache Werte erzeugen. Genauso gibt es Konstruktorfunktionen, die zwar einen Prototypen bereitstellen, aber ohne new aufgerufen werden wollen, beispielsweise Symbol.

Funktionen aus der JavaScript-Laufzeitumgebung, die nicht selbst in JavaScript geschrieben sind, konnten dies schon lange unterscheiden. Für Funktionen, die in JavaScript erstellt wurden, ist eine solche Prüfung seit ECMAScript 2015 mit Hilfe der Pseudoeigenschaft new.target möglich. Bitte lassen Sie sich von dem eigenwilligen Namen nicht verwirren, weder ist new ein Objekt noch ist target eine Eigenschaft davon.

new.target ist in allen Funktionen verfügbar, und enthält undefined, wenn die Funktion wie eine normale Funktion aufgerufen wurde. Wird sie hingegen verwendet, um mit dem Operator new ein Objekt zu erzeugen, verweist new.target auf die Konstruktorfunktion, auf die new angewendet wurde. Dieser Verweis bleibt auch erhalten, wenn Klassen vererbt werden und ein Konstruktor mittels super() den Konstruktor der Basisklasse aufruft.

Beispiel
function Constructor (value) {
  if (new.target === undefined) {
    throw new TypeError( );
  }
  this.key = value;
}

const instance = Constructor('value'); // Type Error

In diesem Beispiel erkennt die Konstruktorfunktion, dass sie ohne new aufgerufen wurde, und bricht ab.

Wenn Sie an Stelle einer Konstruktorfunktion die [[1]] verwenden, übernimmt JavaScript diese Prüfung für Sie.

Alternativen zum Operator new

Statt den Operator new für den Konstruktorenaufruf zu verwenden, kann zu diesem Zweck auch die Methode construct des Standardobjektes Reflect aufgerufen werden, welche als erstes Argument das entsprechende Funktionsobjekt erwartet und als optionales zweites Argument ein Array-ähnliches Objekt mit den Argumenten, die beim Aufruf übergeben werden sollen.


Beispiel
class CustomType {
  logTrue ( ) {
    console.log(true);
  }
}

const instance = Reflect.construct(CustomType);

instance.logTrue( ); // true


Auch hier wird also die jeweilige Funktion über die interne Methode [[Construct]] aufgerufen, unabhängig davon ob es sich um eine gewöhnliche Funktion handelt, oder wie hier in dem Beispiel um eine Klasse. Es ist allerdings auch hier zu beachten, dass sowohl die Klassensyntax als auch das eingebaute Objekt Reflect mit seinen Methoden erst seit der sechsten Edition des Standards ein Teil der Sprache ist. Vor der Verwendung dieser Features sollte also zunächst die Kompatibilität geprüft werden.


Beispiel
const prototype = {
  logTrue ( ) {
    console.log(true);
  }
};

const object = Object.create(prototype);

object.logTrue( ); // true


Schließlich kann zur Erzeugung eines Objektes mit einem bestimmten Prototypen auch die Methode create des Konstruktors Object verwendet werden, das heißt, die Vererbung von Eigenschaften und Methoden kann auch gänzlich ohne einen Konstruktorenaufruf implementiert werden.

Weblinks