Kapitel 8. Werte
Inhaltsverzeichnis
Das Buch kaufen
(Werbung, bitte nicht blockieren.)

Kapitel 8. Werte

JavaScript hat die meisten der Werte, die wir von Programmiersprachen erwarten: Booleans, Zahlen, Zeichenketten, Arrays und so weiter. Alle normalen Werte in JavaScript haben Eigenschaften.[9] Jede Eigenschaft hat einen Schlüssel (oder Namen) und einen Wert. Sie können sich Eigenschaften wie Felder eines Datensatzes vorstellen. Sie verwenden den Punktoperator (.), um auf Eigenschaften zuzugreifen:

> var obj = {}; // create an empty object
> obj.foo = 123;  // write property
123
> obj.foo  // read property
123
> 'abc'.toUpperCase()  // call method
'ABC'

Das Typsystem von JavaScript

Dieses Kapitel gibt einen Überblick über das Typsystem von JavaScript.

JavaScript-Typen

JavaScript hat nur sechs Typen, gemäß Kapitel 8 der ECMAScript-Sprachspezifikation

Ein ECMAScript-Sprachtyp entspricht Werten, die von einem ECMAScript-Programmierer direkt mit der ECMAScript-Sprache manipuliert werden. Die ECMAScript-Sprachtypen sind

  • Undefined, Null
  • Boolean, String, Number und
  • Object

Daher führen Konstruktoren technisch keine neuen Typen ein, obwohl gesagt wird, dass sie Instanzen haben.

Statische Typisierung versus dynamische Typisierung

In einer statisch typisierten Sprache haben Variablen, Parameter und Objekteigenschaften (JavaScript nennt sie Eigenschaften) Typen, die dem Compiler zur Kompilierzeit bekannt sind. Der Compiler kann diese Informationen verwenden, um Typüberprüfungen durchzuführen und den kompilierten Code zu optimieren.

Auch in statisch typisierten Sprachen hat eine Variable auch einen dynamischen Typ, den Typ des Wertes der Variablen zu einem bestimmten Zeitpunkt zur Laufzeit. Der dynamische Typ kann vom statischen Typ abweichen. Zum Beispiel (Java)

Object foo = "abc";

Der statische Typ von foo ist Object; sein dynamischer Typ ist String.

JavaScript ist dynamisch typisiert; Typen von Variablen sind zur Kompilierzeit im Allgemeinen nicht bekannt.

Statische Typprüfung versus dynamische Typprüfung

Wenn Sie Typinformationen haben, können Sie prüfen, ob ein Wert, der in einer Operation verwendet wird (Aufruf einer Funktion, Anwendung eines Operators usw.), den richtigen Typ hat. Statisch typprüfte Sprachen führen diese Art von Prüfung zur Kompilierzeit durch, während dynamisch typprüfte Sprachen dies zur Laufzeit tun. Eine Sprache kann sowohl statisch als auch dynamisch typprüft sein. Wenn eine Prüfung fehlschlägt, erhalten Sie normalerweise eine Art von Fehler oder Ausnahme.

JavaScript führt eine sehr begrenzte Art der dynamischen Typprüfung durch

> var foo = null;
> foo.prop
TypeError: Cannot read property 'prop' of null

Meistens schlagen die Dinge jedoch stillschweigend fehl oder funktionieren. Wenn Sie zum Beispiel auf eine nicht vorhandene Eigenschaft zugreifen, erhalten Sie den Wert undefined

> var bar = {};
> bar.prop
undefined

Coercion (Typumwandlung)

In JavaScript ist der hauptsächliche Weg, mit einem Wert umzugehen, dessen Typ nicht passt, ihn in den richtigen Typ zu coercen. Coercion bedeutet implizite Typkonvertierung. Die meisten Operanden werden coercet:

> '3' * '4'
12

Die integrierten Konvertierungsmechanismen von JavaScript unterstützen nur die Typen Boolean, Number, String und Object. Es gibt keine standardmäßige Möglichkeit, eine Instanz eines Konstruktors in eine Instanz eines anderen Konstruktors zu konvertieren.

Warnung

Die Begriffe stark typisiert und schwach typisiert haben keine allgemein aussagekräftigen Definitionen. Sie werden verwendet, aber normalerweise falsch. Es ist besser, stattdessen statisch typisiert, statisch typprüft usw. zu verwenden.

Primitive Werte im Vergleich zu Objekten

JavaScript trifft eine etwas willkürliche Unterscheidung zwischen Werten:

  • Die primitiven Werte sind Booleans, Zahlen, Zeichenketten, null und undefined.
  • Alle anderen Werte sind Objekte.

Ein Hauptunterschied zwischen den beiden ist, wie sie verglichen werden; jedes Objekt hat eine eindeutige Identität und ist nur (strikt) gleich sich selbst

> var obj1 = {};  // an empty object
> var obj2 = {};  // another empty object
> obj1 === obj2
false

> var obj3 = obj1;
> obj3 === obj1
true

Im Gegensatz dazu werden alle primitiven Werte, die denselben Wert kodieren, als gleich betrachtet

> var prim1 = 123;
> var prim2 = 123;
> prim1 === prim2
true

Die folgenden beiden Abschnitte erläutern primitive Werte und Objekte im Detail.

Primitive Werte

Die folgenden sind alle primitiven Werte (Primitives kurz):

Primitives haben die folgenden Charakteristiken:

Verglichen nach Wert

Der „Inhalt“ wird verglichen

> 3 === 3
true
> 'abc' === 'abc'
true
Immer unveränderlich

Eigenschaften können nicht geändert, hinzugefügt oder entfernt werden:

> var str = 'abc';

> str.length = 1; // try to change property `length`
> str.length      // ⇒ no effect
3

> str.foo = 3; // try to create property `foo`
> str.foo      // ⇒ no effect, unknown property
undefined

(Das Lesen einer unbekannten Eigenschaft gibt immer undefined zurück.)

Ein fester Satz von Typen
Sie können keine eigenen primitiven Typen definieren.

Objekte

Alle nicht-primitiven Werte sind Objekte. Die gängigsten Arten von Objekten sind:

Objekte haben die folgenden Charakteristiken

Verglichen nach Referenz

Identitäten werden verglichen; jedes Objekt hat seine eigene Identität:

> ({} === {})  // two different empty objects
false

> var obj1 = {};
> var obj2 = obj1;
> obj1 === obj2
true
Standardmäßig veränderbar

Sie können Eigenschaften normalerweise frei ändern, hinzufügen und entfernen (siehe Dot Operator (.): Accessing Properties via Fixed Keys)

> var obj = {};
> obj.foo = 123; // add property `foo`
> obj.foo
123
Benutzererweiterbar
Konstruktoren (siehe Layer 3: Constructors—Factories for Instances) können als Implementierungen benutzerdefinierter Typen (ähnlich wie Klassen in anderen Sprachen) betrachtet werden.

undefined und null

JavaScript hat zwei „Nichtwerte“, die fehlende Informationen anzeigen, undefined und null:

undefined und null sind die einzigen Werte, für die jede Art von Eigenschaftszugriff zu einer Ausnahme führt

> function returnFoo(x) { return x.foo }

> returnFoo(true)
undefined
> returnFoo(0)
undefined

> returnFoo(null)
TypeError: Cannot read property 'foo' of null
> returnFoo(undefined)
TypeError: Cannot read property 'foo' of undefined

undefined wird auch manchmal als Metawert verwendet, der Nichtexistenz anzeigt. null hingegen zeigt Leere an. Zum Beispiel gibt ein JSON-Knotenbesucher (siehe Transforming Data via Node Visitors)

  • undefined, um eine Objekteigenschaft oder ein Arrayelement zu entfernen
  • null, um die Eigenschaft oder das Element auf null zu setzen

Vorkommen von undefined und null

Hier überprüfen wir die verschiedenen Szenarien, in denen undefined und null vorkommen.

Vorkommen von undefined

Uninitialisierte Variablen sind undefined:

> var foo;
> foo
undefined

Fehlende Parameter sind undefined:

> function f(x) { return x }
> f()
undefined

Wenn Sie eine nicht existierende Eigenschaft lesen, erhalten Sie undefined:

> var obj = {}; // empty object
> obj.foo
undefined

Und Funktionen geben implizit undefined zurück, wenn nichts explizit zurückgegeben wurde:

> function f() {}
> f()
undefined

> function g() { return; }
> g()
undefined

Vorkommen von null

Prüfung auf undefined oder null

In den folgenden Abschnitten überprüfen wir, wie man individuell auf undefined und null prüft oder ob eines von beiden existiert.

Prüfen auf null

Sie prüfenauf null über strikte Gleichheit:

if (x === null) ...

Prüfen auf undefined

Strikte Gleichheit (===) ist der kanonische Weg, um auf undefined zu prüfen:

if (x === undefined) ...

Sie können auch auf undefined über den typeof-Operator prüfen (typeof: Categorizing Primitives), aber Sie sollten normalerweise den oben genannten Ansatz verwenden.

Prüfen auf entweder undefined oder null

Die meisten Funktionen erlauben Ihnen, einen fehlenden Wert entweder über undefined oder null anzugeben. Eine Möglichkeit, beide zu prüfen, ist über einen expliziten Vergleich:

// Does x have a value?
if (x !== undefined && x !== null) {
    ...
}
// Is x a non-value?
if (x === undefined || x === null) {
    ...
}

Eine andere Möglichkeit ist, die Tatsache auszunutzen, dass sowohl undefined als auch null als false gelten (siehe Truthy and Falsy Values)

// Does x have a value (is it truthy)?
if (x) {
    ...
}
// Is x falsy?
if (!x) {
    ...
}

Warnung

false, 0, NaN und '' gelten ebenfalls als false.

Die Geschichte von undefined und null

Ein einziger Nichtwert könnte die Rollen von sowohl undefined als auch null spielen. Warum hat JavaScript zwei solche Werte? Der Grund ist historisch bedingt.

JavaScript übernahm den Ansatz von Java, Werte in Primitives und Objekte zu unterteilen. Es verwendete auch den Wert von Java für „kein Objekt“, null. Nach dem Vorbild von C (aber nicht Java) wird null zu 0, wenn es zu einer Zahl coercet wird:

> Number(null)
0
> 5 + null
5

Denken Sie daran, dass die erste Version von JavaScript keine Ausnahmebehandlung hatte. Daher mussten Ausnahmefälle wie uninitialisierte Variablen und fehlende Eigenschaften über einen Wert angezeigt werden. null wäre eine gute Wahl gewesen, aber Brendan Eich wollte zu dieser Zeit zwei Dinge vermeiden

  • Der Wert sollte nicht die Konnotation einer Referenz haben, da es um mehr als nur Objekte ging.
  • Der Wert sollte nicht zu 0 coercen, da dies Fehler schwerer erkennbar macht.

Infolgedessen fügte Eich undefined als zusätzlichen Nichtwert zur Sprache hinzu. Es coercet zu NaN

> Number(undefined)
NaN
> 5 + undefined
NaN

Ändern von undefined

undefined ist eine Eigenschaft des globalen Objekts (und somit eine globale Variable; siehe The Global Object). Unter ECMAScript 3 mussten Sie Vorkehrungen beim Lesen von undefined treffen, da es leicht war, seinen Wert versehentlich zu ändern. Unter ECMAScript 5 ist das nicht notwendig, da undefined schreibgeschützt ist.

Um gegen ein geändertes undefined zu schützen, waren zwei Techniken beliebt (sie sind für ältere JavaScript-Engines immer noch relevant)

Technik 1

Überschatten Sie das globale undefined (das möglicherweise den falschen Wert hat)

(function (undefined) {
    if (x === undefined) ...  // safe now
}());  // don’t hand in a parameter

Im obigen Code hat undefined garantiert den richtigen Wert, da es sich um einen Parameter handelt, dessen Wert nicht durch den Funktionsaufruf bereitgestellt wurde.

Technik 2

Vergleichen Sie mit void 0, was immer (das richtige) undefined ist (siehe The void Operator)

if (x === void 0)  // always safe

Wrapper-Objekte für Primitives

Die drei primitiven Typen boolean, number und string haben entsprechende Konstruktoren: Boolean, Number, String. Ihre Instanzen (sogenannte Wrapper-Objekte) enthalten (wrap) primitive Werte. Die Konstruktoren können auf zwei Arten verwendet werden:

  • Als Konstruktoren erstellen sie Objekte, die weitgehend inkompatibel mit den primitiven Werten sind, die sie wrappen

    > typeof new String('abc')
    'object'
    > new String('abc') === 'abc'
    false
  • Als Funktionen konvertieren sie Werte in die entsprechenden primitiven Typen (siehe Functions for Converting to Boolean, Number, String, and Object). Dies ist die empfohlene Methode der Konvertierung

    > String(123)
    '123'

Tipp

Es wird als bewährte Praxis angesehen, Wrapper-Objekte zu vermeiden. Sie benötigen sie normalerweise nicht, da es nichts gibt, was Objekte tun können, was Primitives nicht können (mit Ausnahme der Veränderbarkeit). (Dies unterscheidet sich von Java, von dem JavaScript den Unterschied zwischen Primitives und Objekten geerbt hat!)

Wrapper-Objekte unterscheiden sich von Primitives

Primitive Werte wie 'abc' unterscheiden sich grundlegend von Wrapper-Instanzen wie new String('abc'):

> typeof 'abc'  // a primitive value
'string'
> typeof new String('abc')  // an object
'object'
> 'abc' instanceof String  // never true for primitives
false
> 'abc' === new String('abc')
false

Wrapper-Instanzen sind Objekte, und es gibt keine Möglichkeit, Objekte in JavaScript zu vergleichen, nicht einmal über den nachgiebigen Gleichheitsoperator == (siehe Equality Operators: === Versus ==)

> var a = new String('abc');
> var b = new String('abc');
> a == b
false

Wrappen und Unwrappen von Primitives

Es gibt einen Anwendungsfall für Wrapper-Objekte: Sie möchten einer primitiven Wert Eigenschaft hinzufügen. Dann wrappen Sie das Primitive und fügen dem Wrapper-Objekt Eigenschaften hinzu. Sie müssen den Wert unwrappen, bevor Sie damit arbeiten können.

Wrappen Sie ein Primitive, indem Sie einen Wrapper-Konstruktor aufrufen

new Boolean(true)
new Number(123)
new String('abc')

Entpacken Sie ein Primitive durch Aufrufen der Methode valueOf(). Alle Objekte haben diese Methode (wie in Conversion to Primitive) besprochen

> new Boolean(true).valueOf()
true
> new Number(123).valueOf()
123
> new String('abc').valueOf()
'abc'

Das richtige Konvertieren von Wrapper-Objekten in Primitives extrahiert Zahlen und Zeichenketten, aber keine Booleans:

> Boolean(new Boolean(false))  // does not unwrap
true
> Number(new Number(123))  // unwraps
123
> String(new String('abc'))  // unwraps
'abc'

Der Grund dafür wird in Converting to Boolean erklärt.

Primitives leihen sich ihre Methoden von Wrappern

Primitives haben keine eigenen Methoden und leihen sie sich von Wrappern

> 'abc'.charAt === String.prototype.charAt
true

Sloppy Mode und Strict Mode behandeln dieses Ausleihen unterschiedlich. Im Sloppy Mode werden Primitives on the fly in Wrapper konvertiert

String.prototype.sloppyMethod = function () {
    console.log(typeof this); // object
    console.log(this instanceof String); // true
};
''.sloppyMethod(); // call the above method

Im Strict Mode werden Methoden vom Wrapper-Prototyp transparent verwendet

String.prototype.strictMethod = function () {
    'use strict';
    console.log(typeof this); // string
    console.log(this instanceof String); // false
};
''.strictMethod(); // call the above method

Typumwandlung (Type Coercion)

Typumwandlung bedeutetdie implizite Konvertierung eines Wertes eines Typs in einen Wert eines anderen Typs. Die meisten JavaScript-Operatoren, -Funktionen und -Methoden wandeln Operanden und Argumente in die Typen um, die sie benötigen. Zum Beispiel werden die Operanden des Multiplikationsoperators (*) in Zahlen umgewandelt:

> '3' * '4'
12

Als weiteres Beispiel, wenn einer der Operanden eine Zeichenkette ist, konvertiert der Plus-Operator (+) den anderen in eine Zeichenkette

> 3 + ' times'
'3 times'

Typumwandlung kann Fehler verbergen

Daher beschwert sich JavaScript selten über einen falschen Typ eines Wertes. Programme erhalten zum Beispiel normalerweise Benutzereingaben (von Online-Formularen oder GUI-Widgets) als Zeichenketten, auch wenn der Benutzer eine Zahl eingegeben hat. Wenn Sie eine Zahl-als-Zeichenkette wie eine Zahl behandeln, erhalten Sie keine Warnung, nur unerwartete Ergebnisse. Zum Beispiel

var formData = { width: '100' };

// You think formData.width is a number
// and get unexpected results
var w = formData.width;
var outer = w + 20;

// You expect outer to be 120, but it’s not
console.log(outer === 120);  // false
console.log(outer === '10020');  // true

In Fällen wie dem vorherigen sollten Sie frühzeitig in den entsprechenden Typ konvertieren

var w = Number(formData.width);

Funktionen zur Konvertierung in Boolean, Number, String und Object

Die folgenden Funktionen sind die bevorzugte Methode, umeinen Wert in einen Boolean, eine Zahl, eine Zeichenkette oder ein Objekt zu konvertieren:

Boolean() (siehe Converting to Boolean)

Konvertiert einen Wert in einen Boolean. Die folgenden Werte werden in false konvertiert; dies sind die sogenannten „falsy“-Werte:

  • undefined, null
  • false
  • 0, NaN
  • ''

Alle anderen Werte gelten als „truthy“ und werden in true konvertiert (einschließlich aller Objekte!).

Number() (siehe Converting to Number)

Konvertiert einen Wert in eine Zahl:

  • undefined wird zu NaN.
  • null wird zu 0.
  • false wird zu 0, true wird zu 1.
  • Strings werden geparst.
  • Objekte werden zuerst in Primitives konvertiert (wird gleich besprochen) und dann in Zahlen.
String() (siehe Converting to String)

Konvertiert einen Wert in eine Zeichenkette. Er hat offensichtliche Ergebnisse für alle Primitives. Zum Beispiel:

> String(null)
'null'
> String(123.45)
'123.45'
> String(false)
'false'

Objekte werden zuerst in Primitives konvertiert (wird gleich besprochen), die dann in Zeichenketten konvertiert werden.

Object() (siehe Converting Any Value to an Object)

Konvertiert Objekte in sich selbst, undefined und null in leere Objekte und Primitives in gewrappte Primitives. Zum Beispiel:

> var obj = { foo: 123 };
> Object(obj) === obj
true

> Object(undefined)
{}
> Object('abc') instanceof String
true

Beachten Sie, dass Boolean(), Number(), String() und Object() als Funktionen aufgerufen werden. Sie verwenden sie normalerweise nicht als Konstruktoren. Dann erstellen sie Instanzen von sich selbst (siehe Wrapper Objects for Primitives).

Algorithmus: ToPrimitive()—Konvertierung eines Wertes in ein Primitiv

Um einen Wert in eine Zahl oder eine Zeichenkette zu konvertieren, wird er zuerst in einen beliebigen primitiven Wert konvertiert, der dann in den endgültigen Typ konvertiert wird (wie in Functions for Converting to Boolean, Number, String, and Object) besprochen).

Die ECMAScript-Spezifikation hat eine interne Funktion, ToPrimitive() (die von JavaScript aus nicht zugänglich ist), die diese Konvertierung durchführt. Das Verständnis von ToPrimitive() ermöglicht es Ihnen zu konfigurieren, wie Objekte in Zahlen und Zeichenketten konvertiert werden. Sie hat die folgende Signatur

ToPrimitive(input, PreferredType?)

Der optionale Parameter PreferredType gibt den endgültigen Typ der Konvertierung an: er ist entweder Number oder String, je nachdem, ob das Ergebnis von ToPrimitive() in eine Zahl oder eine Zeichenkette konvertiert wird.

Wenn PreferredType Number ist, führen Sie die folgenden Schritte aus

  1. Wenn input primitiv ist, geben Sie ihn zurück (es gibt nichts mehr zu tun).
  2. Andernfalls ist input ein Objekt. Rufen Sie input.valueOf() auf. Wenn das Ergebnis primitiv ist, geben Sie es zurück.
  3. Andernfalls rufen Sie input.toString() auf. Wenn das Ergebnis primitiv ist, geben Sie es zurück.
  4. Andernfalls werfen Sie eine TypeError (die das Fehlschlagen der Konvertierung von input in ein Primitiv anzeigt).

Wenn PreferredType String ist, werden Schritt 2 und 3 vertauscht. PreferredType kann auch weggelassen werden; es wird dann für Daten als String und für alle anderen Werte als Number betrachtet. Dies ist, wie die Operatoren + und == ToPrimitive() aufrufen.

Beispiele: ToPrimitive() in Aktion

Die Standardimplementierung von valueOf() gibt this zurück, während die Standardimplementierung von toString() Typinformationen zurückgibt:

> var empty = {};
> empty.valueOf() === empty
true
> empty.toString()
'[object Object]'

Daher überspringt Number() valueOf() und konvertiert das Ergebnis von toString() in eine Zahl; das heißt, es konvertiert '[object Object]' in NaN

> Number({})
NaN

Das folgende Objekt passt valueOf() an, was Number() beeinflusst, aber nichts für String() ändert

> var n = { valueOf: function () { return 123 } };
> Number(n)
123
> String(n)
'[object Object]'

Das folgende Objekt passt toString() an. Da das Ergebnis in eine Zahl konvertiert werden kann, kann Number() eine Zahl zurückgeben

> var s = { toString: function () { return '7'; } };
> String(s)
'7'
> Number(s)
7



[9] Technisch gesehen haben primitive Werte keine eigenen Eigenschaften, sie leihen sie von Wrapper-Konstruktoren. Aber das geschieht hinter den Kulissen, so dass man es normalerweise nicht sieht.

Weiter: 9. Operatoren