Startseite / Artikel

Was der TypeScript-Compiler erkennt, was beim JavaScript durchgeht

Vergleichen Sie JavaScript und TypeScript Seite an Seite: Typinferenz, Annotationen, Primitive, any, Unionen sowie typisierte Funktionen – dazu, was tsc tatsächlich anzeigt und ausgibt.

Verlag: REACTAPP.TOP14 Min. Lesezeit3122 Wörter

Quellen: JavaScript vs TypeScript: Understanding Type Safety

JavaScript kann fast alles ausführen, was man ihm gibt – genau deshalb treten einige Fehler erst in der Produktion zutage. TypeScript fügt einen Überprüfungsschritt vor dem Ausführen des Codes hinzu, doch es ist leicht, falsch zu verstehen, wofür dieser Schritt sorgt und vor was er nicht schützt. Dieser Leitfaden verwendet kleine, typisierbare Beispiele – von der Neuzuweisung von Variablen bis hin zu typisierten Funktionen – damit Sie genau erkennen können, wo JavaScript schweigt, wo der TypeScript-Compiler eingreift und wie Sie ihn konfigurieren können, damit Fehler tatsächlich einen Build blockieren.

Dynamische Typisierung versus statische Typisierung

JavaScript ist dynamisch typisiert. Eine Variable hat keinen festen Typ; der Typ gehört dem Wert, den sie aktuell enthält, wird während des Programmablaufs bestimmt und kann jederzeit ändern.

TypeScript ist ein statisch typisierter Superset von JavaScript. Jedes gültige JavaScript-Programm ist auch syntaktisch gültiges TypeScript, doch TypeScript verfügt über einen Typprüfer, der den Code zur Kompilierzeit analysiert, bevor etwas ausgeführt wird. Anschließend entfernt der Prüfer die Typinformationen und gibt reines JavaScript aus.

Alles Weitere in diesem Leitfaden ergibt sich aus diesem einzigen Unterschied. Die Festlegung der Typen zur Laufzeit macht JavaScript flexibel, lässt aber Fehler unentdeckt durchgehen. Die Vorabfestlegung der Typen macht TypeScript strenger, weist jedoch viele Fehler frühzeitig auf – das ist es, was die Leute meinen, wenn sie von der Typsicherheit von TypeScript sprechen. Streng genommen handelt es sich bei dieser Sicherheit um eine Kompilierzeitgarantie: Sie gilt nur soweit, wie der Prüfer etwas erkennen kann.

Wenn eine Variable ihren Typ ändert

In reinem JavaScript beginnt man mit einer numerischen Variable und überprüft deren Typ mithilfe von typeof:

let age = 30;
console.log(typeof(age));  // output is number

Eine paar Zeilen später hindert Sie nichts daran, einen String in derselben Variablen zu speichern:

age = "John";
console.log(age);
console.log(typeof(age)); // string

JavaScript akzeptiert dies ohne Warnungen oder Fehler. Der erste typeof-Aufruf gibt "number" an und der zweite "string": eine Variable mit zwei unverwandten Typen während ihrer Lebensdauer. In einem kleinen, harmlosen Skript ist das kein Problem; in einer großen Codebasis wird jeder Code, der weiterhin eine Zahl von age erwartet, nun in unerwarteter Weise reagieren – weit entfernt von der Zeile, die den Fehler verursacht hat.

TypeScript lehnt solchen Code ab:

let data = 2;
data = "two"; // ❌ Error: Type 'string' is not assignable to type 'number'

Wenn TypeScript den Code let data = 2; sieht, schließt es aus dem Anfangswert, dass data den Typ number hat, und behält diesen Typ während der gesamten Lebensdauer der Variable bei. Dieses Verhalten wird als Typableitung bezeichnet. Die Zuweisung von "two" in der nächsten Zeile wird daher vom Compiler bereits vor dem Ausführen des Programms abgelehnt.

Anstelle der Typableitung können Sie den Typ auch selbst angeben:

let data: number = 2;

Der Teil : number ist eine ausschließlich in TypeScript verwendete Syntax und existiert in JavaScript nicht. Was die Typsicherheit betrifft, sind beide Formen äquivalent; die annotierte Version macht lediglich die Absicht für alle sichtbar, die den Code später lesen – einschließlich Ihnen in sechs Monaten.

Die zu merkende Regel: JavaScript erlaubt es einer Variablen, so oft den Typ zu wechseln, wie man möchte, während TypeScript den Typ einer Variablen einmal festlegt – entweder durch eine Anmerkung oder durch ihren ersten Wert – und ihn danach durchsetzt.

Die Falle aus Zeichenfolge und Zahl

Das vorherige Beispiel zeigt, dass TypeScript Typen überprüft. Das nächste zeigt, warum das wichtig ist.

Auch JavaScript erlaubt es wieder gerne, dass eine Variable, die ursprünglich eine Zeichenfolge enthielt, durch eine Zahl überschrieben wird:

let message = "Hello";
let num = 2;
message = 20; // totally allowed in JS

Nun die klassische Überraschung:

let result = "5" + 3;
console.log(result); // "53", not 8

Man könnte mit 8 rechnen, weil "5" wie eine Zahl aussieht. Doch "5" ist ein String, und wenn der +-Operator auf Strings auf beiden Seiten stößt, führt JavaScript eine Zeichenkettenverknüpfung statt einer arithmetischen Berechnung durch. Es wandelt 3 in Text um und verknüpft die beiden, wodurch "53" entsteht. JavaScript führt dies ohne Probleme aus, weil es die Typen nicht im Voraus überprüft; es tut einfach das, was der Code zum Zeitpunkt der Ausführung wörtlich vorsieht.

Was TypeScript mit demselben Code macht

Hier ist die TypeScript-Version, bei der die Typen ausdrücklich angegeben sind:

let num1: string = "5";
let num2: number = 3;

Und die Addition selbst:

let add = num1 + num2; // expected 8, but num1 is a string and num2 is a number
console.log(add); // actual result is 53

Der ausgegebene Wert ist weiterhin 53. Hier gehen viele Erklärungen falsch, daher ist Genauigkeit wichtig. TypeScript erlaubt absichtlich string + number, da die Verknüpfung von Text mit einer Zahl in JavaScript üblich und zulässig ist. Der Prüfer lehnt die Ausdrucksform nicht ab; er schließt lediglich daraus, dass add den Typ string hat.

Der Operatorfehler, den Sie möglicherweise gesehen haben, sieht so aus:

Operator '+' cannot be applied to types 'string' and 'number'.

TypeScript gibt diese Art von Meldung für Operandenkombinationen aus, die es als sinnlos betrachtet – beispielsweise wenn ein boolean mit einer number addiert wird oder Werte mit völlig unterschiedlichen Typen zusammengefügt werden. Das ist nicht das Ergebnis von num1 + num2 oben.

In diesem Szenario schützt TypeScript Sie erst dann, wenn das Ergebnis als Zahl verwendet wird. Hätten Sie let add: number = num1 + num2; geschrieben, hätte der Compiler gemeldet, dass der Typ string nicht auf den Typ number zugewiesen werden kann. Ebenso würde das Übergeben von add an eine Funktion, die eine number erwartet, fehlschlagen. In der Praxis wird so der „5“ + 3-Fehler in einer typisierten Codebasis aufgedeckt: nicht am Pluszeichen, sondern dort, wo eine versehentliche Zeichenkette auf Code trifft, dem eigentlich eine Zahl versprochen wurde.

Warum Fehler Ihr Programm standardmäßig nicht stoppen

Es gibt noch eine weitere Nuance. Selbst wenn tsc einen Typfehler findet, gibt es standardmäßig weiterhin die kompilierte .js-Datei aus. Das Ausführen dieser Datei mit node führt lediglich reines JS aus – nichts weiter. Alle Typinformationen wurden entfernt, und es bleibt nur die Laufzeitlogik übrig.

Das ist das Wesen von TypeScript: Es ändert nicht die Funktionsweise Ihres Codes zur Laufzeit. Es meldet Probleme in Ihrem Editor und während der Kompilierung, noch bevor Sie etwas ausführen. Wenn Sie möchten, dass der Compiler bei Typfehlern auf die Erstellung einer Ausgabe verzichtet, aktivieren Sie diese Option in tsconfig.json:

{
  "compilerOptions": {
    "noEmitOnError": true
  }
}

Wenn noEmitOnError aktiviert ist, erreicht Code mit Typfehlern niemals node, da tsc ohne Erstellung der JavaScript-Datei abbricht. In den meisten echten Projekten wird derselbe Effekt in CI erreicht, indem tsc --noEmit als separate Typüberprüfungsschritt ausgeführt wird und der Build bei jedem Fehler fehlschlägt.

Der entscheidende Punkt: Der Wert von TypeScript liegt nicht in einer anderen Laufzeitverhalten, denn kompiliertes TypeScript ist JavaScript. Sein Wert besteht darin, Unstimmigkeiten bereits vor dem Ausführen aufzudecken und Ihnen die Möglichkeit zu geben, Fehler zu beheben, die JavaScript sonst stillschweigend weiterlaufen ließe.

Anmerkungen und Inferenz

Diese beiden Begriffe beschreiben gegensätzliche Wege, zum selben Ergebnis zu gelangen. Betrachten wir eine annotierte Deklaration:

let age: number = 30;

Stück für Stück betrachtet:

  • let ist das Deklarations Schlüsselwort
  • age ist der Variablename
  • 30 ist der zugewiesene Wert
  • number ist die Typangabe
  • : number, das Kolon gefolgt vom Typ, ist die Typanmerkung

Eine Anmerkung teilt TypeScript explizit mit, welchen Typ eine Variable hat, indem sie eine Kolonsyntax wie : number, : string oder : boolean verwendet. Bei der Inferenz wird das Gegenteil gemacht: Man weglässt den Typ, und TypeScript schließt ihn aus dem zugewiesenen Wert ab.

let age = 30;        // inference — TS decides this is a "number" on its own
let city: string = "Delhi"; // annotation — you told TS explicitly

Sowohl die eine als auch die andere Methode bieten denselben Schutz, denn sobald der Typ von Ihnen oder vom Compiler festgelegt wird, ist er gesperrt. Anmerkungen dienen hauptsächlich der Klarheit. Sie sind am nützlichsten, wenn eine Variable ohne Anfangswert deklariert wird, da es nichts gibt, woraus man schließen könnte, sowie bei Funktionssignaturen und öffentlichen APIs, wo sie die Absicht dokumentieren. Bei lokalen Variablen mit einem offensichtlichen Anfangswert überlassen die meisten Teams die Schlussfolgerung dem Compiler.

Primitiv- und nicht-primitive Typen

Eine hilfreiche Art, die Typen, auf die man früh stoßen wird, zu gruppieren:

  • Primitivtypen wie number, string, boolean, undefined und null speichern jeweils genau einen Wert auf einmal.
  • Nicht-primitive, also strukturierte Typen wie Arrays, Objekte, Tupel, Funktionen sowie durch Klassen und Schnittstellen beschriebene Strukturen kombinieren mehrere Werte – beispielsweise ein Array mit Noten oder ein Benutzerprofil mit Namen und E-Mail-Adresse.
  • Ein Primitivtyp ist eine Box, die einen einzigen Eintrag enthält; ein strukturierter Typ hingegen ist eine Kiste, die viele Boxen enthält.

    Zwei weitere Konstrukte tauchen in Anfängerlisten meist neben den Primitivtypen auf und werden im Folgenden erläutert: any sowie Union-Typen. Streng genommen gehört keiner von ihnen zu den Primitivtypen. any dient als Ausweg, um Überprüfungen zu umgehen, während eine Union eine Möglichkeit darstellt, andere Typen miteinander zu kombinieren. Sie werden neben den Primitivtypen aufgeführt, weil sie in der Regel mit einzelnen Werten verwendet werden.

    Primitivtypen in der Praxis

    Zahlen

    Die number-Typklasse in TypeScript umfasst sowohl Ganzzahlen als auch Fließkommazahlen. Es gibt keine separaten int- oder float-Typen wie in vielen anderen Sprachen, denn JavaScript speichert normale Zahlen selbst als 64-Bit-Fließkommawerte.

    let age = 20;
    let price = 26.2;
    let big = 24757689;
    

    Beim Ausgeben sowie beim Überprüfen mit typeof:

    console.log("Age :", age);
    console.log("price:", price);
    console.log(big);
    console.log(typeof(age)); // "number"
    

    Ganzzahlen, Dezimalzahlen und große Werte sind alle einfach number, und typeof gibt für jeden von ihnen „number“ an. Für Ganzzahlen, die außerhalb des sicheren Bereichs dieses Fließkommaformats liegen, bietet JavaScript einen separaten bigint-Typ.

    Zeichenketten

    Eine Zeichenkette ist eine Abfolge von Zeichen: ein Wort, ein Satz oder jeder Text. Sie können Zeichenkettenliteral mit einfachen Anführungszeichen, doppelten Anführungszeichen oder Backticks schreiben.

    let firstname: string = 'JOHN';
    let lastname: string = "SINGH";
    console.log("Hello", firstname, lastname); // Hello JOHN SINGH
    

    Wenn mehrere durch Kommas getrennte Argumente an console.log übergeben werden, werden sie mit Leerzeichen getrennt ausgegeben – deshalb wird hier der vollständige Name angezeigt. Eine ähnliche Zeile tut jedoch nicht das, was sie zu tun scheint:

    // let greeting: string = "Hello", firstname, lastname; // ❌ this does NOT concatenate
    

    In einer let-Anweisung trennt ein Komma mehrere Deklarationen voneinander; es verbindet keinen Text. Diese Zeile deklariert drei separate Variablen, greeting, firstname und lastname, wobei nur greeting einen Wert erhält. Wenn firstname und lastname bereits im selben Scope vorhanden sind, wird TypeScript ebenfalls mitteilen, dass sie erneut deklariert werden.

    Kommas eignen sich also zur Ausgabe mehrerer Werte, doch um einen zusammengefügten Satz in einer Variablen zu speichern, sollte man ein Template Literal verwenden: einen in Backticks eingeschlossenen String mit ${}-Platzhaltern für Ausdrücke.

    let firstname: string = 'JOHN';
    let lastname: string = "SINGH";
    let greeting: string = `Hello ${firstname} ${lastname}`;
    console.log(greeting); // Hello JOHN SINGH
    

    Template Literale sind die empfohlene Methode, um Text und Werte zu kombinieren. Sie sind übersichtlicher als Ketten aus +-Konkatenationen und unterstützen außerdem Zeichenketten, die über mehrere Zeilen reichen.

    Boolesche Werte

    Ein boolescher Wert kann nur zwei mögliche Werte annehmen: true und false.

    let isStudent: boolean = true;
    let hasJob: boolean = false;
    

    Protokollierung eines booleschen Wertes und Überprüfung seines Typs:

    console.log("Is student?", isStudent); // true
    console.log(typeof (isStudent)); // "boolean"
    

    Boolesche Werte steuern alle bedingten Anweisungen in Ihrem Code: if-Anweisungen, Schleifenbedingungen sowie alle Stellen, an denen eine Ja-oder-Nein-Entscheidung getroffen wird.

    Null und undefined

    Sowohl null als auch undefined bezeichnen das Fehlen eines Wertes, haben aber unterschiedliche Bedeutungen. Beginnen Sie mit einer deklarierten Variablen, der nie ein Wert zugewiesen wird:

    let emptyvalue;
    console.log(typeof emptyvalue); // "undefined"
    

    Eine deklarierte, aber unzugewiesene Variable enthält den Wert undefined, was bedeutet, dass die Variable existiert, aber noch kein Wert in ihr gespeichert wurde.

    Man kann Variablen auch explizit die Typen null und undefined zuweisen:

    let emptyvalue: null = null;
    let value: undefined = undefined
    

    Vergleichen Sie anschließend, wie jede davon ausgegeben wird, zusammen mit einer typisierten Variablen, der kein Wert zugewiesen wurde:

    console.log(emptyvalue); // null
    console.log(value);      // undefined
    let price: number;
    console.log(price); // undefined - declared, but not yet assigned any value
    

    Im einfachen Wortlaut lautet der Unterschied:

    • undefined bedeutet, dass eine Variable existiert, aber kein Wert erhalten hat; es handelt sich um den standardmäßigen leeren Zustand.
    • null bedeutet, dass ein Entwickler die Variable absichtlich auf „keinen Wert“ gesetzt hat; es handelt sich um eine bewusste, explizite Leere.

    Eine Typangabe wie : number beschränkt, was eine Variable später enthalten darf; sie liefert keinen Anfangswert. Beim Ausführen gibt daher price undefined aus. Beachten Sie jedoch, dass bei aktiviertem strict-Modus, was in modernen Projekten üblich ist, TypeScript einen Fehler in dieser console.log(price)-Zeile meldet, weil die Variable vor der Zuweisung verwendet wird. Diese Überprüfung ist ein weiteres Beispiel dafür, wie der Compiler Fehler erkennt, die reines JavaScript zugelassen würde.

    In alltäglichem Code werden Sie selten eine Variable rein als null oder undefined annotieren; diese Beispiele dienen dazu, zu zeigen, wie sich die beiden leeren Zustände verhalten. Weitaus häufiger ist eine Union wie string | null für einen Wert, der fehlen kann.

    Der any-Typ: Die Überprüfung ausschalten

    Verwenden Sie any nur selten und bewusst, denn dadurch werden genau die Überprüfungen deaktiviert, für die TypeScript konzipiert wurde. Eine any-Variable akzeptiert jede Art von Wert und kann nach Belieben auf einen anderen Typ umgewidmet werden, genauso wie in herkömmlichem JavaScript.

    let value: any = "abcd";
    console.log(typeof value); // "string"
    

    Wenn man sie zunächst auf eine Zahl und anschließend auf einen Booleschen Wert umwidmet:

    value = 123;
    console.log(typeof value); // "number"
    value = true;
    console.log(typeof value); // "boolean"
    

    Die Variable wechselt ohne jeglichen Widerspruch von String zu Zahl zu Booleschem Wert, weil die Annotation dem Compiler mitgeteilt hat, sie nicht überprüfen zu sollen.

    Es gibt legitime Anwendungsfälle. Wenn man einen Typ tatsächlich nicht im Voraus kennen kann – beispielsweise bei unvorhersehbaren externen Daten oder während einer schrittweisen Migration von JavaScript, wenn es nicht praktikabel ist, alles auf einmal einzutippen – hilft any, weiterzukommen. Eine übermäßige Verwendung führt jedoch zu fehlenden Autocomplete-Funktionen, Kompilierfehlern sowie zum Verlust des Schutznets, der ursprünglich die Verwendung von TypeScript motiviert hat. Nutzen Sie es als letztes Mittel, nicht als Gewohnheit. Für Daten unbekannter Struktur ist unknown in der Regel die bessere Wahl, da es Sie zwingt, den Wert vor der Verwendung zu überprüfen. Unser Leitfaden zur Ersetzung von any durch typsichere Muster behandelt die Alternativen ausführlich.

    Union-Typen: eine kontrollierte Auswahl an Optionen

    Ein Union-Typ erlaubt es einem Wert, zu einem der mehreren spezifischen Typen zu gehören. Im Gegensatz zu any akzeptiert er nicht alles, sondern nur die von Ihnen aufgelisteten Typen. „Union“ ist kein Schlüsselwort; es handelt sich um den Namen des Musters, der mit dem Pipe-Symbol | geschrieben wird.

    let id: number | string | boolean;
    

    Zuweisen der jeweiligen erlaubten Type nacheinander:

    id = "123abc";
    console.log(id);
    id = 12;
    console.log(id);
    id = true;
    console.log(id);
    

    Die Variable id kann einen Zahlwert, eine Zeichenkette oder einen Booleschen Wert enthalten und nichts anderes. Die Zuweisung eines Arrays wie [1, 2, 3] wird sofort abgelehnt, da Arrays nicht zum Union-Typ gehören. Dadurch erhalten Sie genau dort Flexibilität, wo sie benötigt wird; reale Identifikatoren kommen beispielsweise manchmal als Zahlen und manchmal als Zeichenketten vor, während gleichzeitig klare Grenzen für das Erlaubte gesetzt werden. Bevor Sie einen Wert aus einer Union auf typspezifische Weise verwenden, erwartet TypeScript, dass Sie ihn zunächst eingrenzen, beispielsweise mit einer typeof-Prüfung.

    Funktionen typisieren

    Funktionen bündeln eine einzelne Aufgabe in Code, den Sie wiederverwenden können. Sie definieren sie einmal und rufen sie so oft und wo immer Sie sie benötigen.

    Eine Funktion ohne Parameter

    Die einfachste Version hat ihre Werte fest kodiert:

    function sum() {
      console.log(10 + 20);
    }
    

    Die Definition einer Funktion führt sie nicht aus. Es passiert nichts, bis Sie sie aufrufen:

    sum(); // calling the function → prints 30
    

    Diese Trennung ist der Schlüssel: Schreiben Sie die Logik einmal und führen Sie sie auf Anfrage aus.

    Typisierte Parameter hinzufügen

    Funktionen werden erst wirklich wiederverwendbar, wenn sie Parameter entgegennehmen – Platzhalter, die es demselben Code ermöglichen, bei jedem Aufruf mit unterschiedlichen Werten zu arbeiten.

    function sum(x: number, y: number) {
      console.log(x + y);
    }
    

    Aufruf mit zwei Zahlen:

    sum(10, 20); // prints 30
    

    Nun ist sum nicht mehr an 10 und 20 gebunden; sum(5, 7) oder sum(100, 1) funktionieren genauso gut. Der : number nach jedem Parameter ist eine Typangabe für diesen Parameter, wodurch TypeScript einen Aufruf wie sum("hello", 20) bereits vor dem Ausführen des Codes ablehnt. Genau hier zeigt sich in der Regel der Nutzen von TypeScript: an den Funktionsgrenzen, wo ein Teil eines Programms Daten an einen anderen übergeben wird.

    Diese Version gibt ihr Ergebnis weiterhin nur mit console.log aus. Sie gibt dem Aufrufer nichts zurück, was für den nächsten Schritt wichtig ist.

    Einen Rückgabetyp deklarieren

    Eine Funktion, die ihr Ergebnis zurückgibt, ist weitaus nützlicher:

    function sum(x: number, y: number): number {
      return x + y;
    }
    

    Aufnahme und Verwendung des zurückgegebenen Wertes:

    let res: number = sum(10, 20);
    console.log(res); // 30
    

    Verglichen mit der vorherigen Version haben sich drei Dinge geändert:

    • return x + y; gibt den Wert an den Aufrufer zurück, anstatt ihn innerhalb der Funktion auszugeben.
    • Der : number nach der Parameterliste, in (x: number, y: number): number, ist die Return-Typ-Anmerkung. Sie garantiert, dass die Funktion immer eine Zahl zurückgibt, und ein Rückgabewert als Zeichenkette würde zu einem Kompilierfehler führen.
    • Weil sum nun einen Wert zurückgibt, kann let res: number = sum(10, 20); diesen zum späteren Gebrauch speichern: z. B. zur Protokollierung, zur Übermittlung an eine andere Funktion oder zur Kombination mit anderen Ergebnissen.

    Eine Funktion, die ihr Ergebnis zurückgibt, kann in andere Logiken eingebunden werden; eine Funktion, die nur protokolliert, ist ein Sackgasseneffekt, dessen Ausgabe nicht wiederverwendet werden kann. TypeScript kann oft Return-Typen ableiten, doch ihre explizite Angabe in exportierten Funktionen schützt davor, versehentlich zu ändern, was eine Funktion zurückgibt.

    Zusammenfassung

    • JavaScript bestimmt die Typen zur Laufzeit und vertraut vollständig darauf, was zwar flexibel ist, aber zu stillen Fehlern wie "5" + 3, das "53" ergibt, führen kann.
    • TypeScript legt den Typ jeder Variablen durch Annotationen oder Schlussfolgerungen fest und überprüft ihn zur Kompilierzeit, bevor der Code ausgeführt wird.
    • TypeScript lehnt nicht jede fragwürdige Ausdrucksweise ab; die Verknüpfung von Zeichenketten mit Zahlen ist erlaubt, und der Schutz tritt ein, wenn die resultierende Zeichenkette auf Code trifft, der eine Zahl erwartet.
    • Typfehler stoppen standardmäßig nicht die Ausgabe; verwenden Sie noEmitOnError oder einen Schritt mit tsc --noEmit in CI, um zu verhindern, dass sie einen Build blockieren.
    • Bewahren Sie any für echte Unbekannte auf, bevorzugen Sie Union-Typen sowie unknown für mehr Flexibilität, und annotieren Sie Funktionsparameter sowie Rückgabetypen dort, wo Daten Grenzen überschreiten.

    Das Ausführungsergebnis von JavaScript- und TypeScript-Code ist identisch. Was sich ändert, ist der Zeitpunkt, an dem man feststellt, dass etwas nicht in Ordnung ist: im Editor und nicht vor den Nutzern.