Aufwärmen · Aufgabe 1 von 7
Zum Aufwärmen aus JavaScript: Keine der beiden Funktionen gibt einen Wert zurück. Was gibt das aus?
function noop() {
return;
}
const values = [noop(), [1, 2].forEach((n) => n * 2)];
console.log(values);// B4.4 · ca. 30 Min. · Einstieg
Nach dieser Lektion typisieren Sie, was eine Funktion zurückgibt, wenn nichts Brauchbares zurückkommt, nehmen beliebige Objekte oder Werte sicher an und deklarieren, was this ist.
Danach können Sie
Aufwärmen · Aufgabe 1 von 7
function noop() {
return;
}
const values = [noop(), [1, 2].forEach((n) => n * 2)];
console.log(values);Vorhersagen · Aufgabe 2 von 7
type VoidFunc = () => void;
const f1: VoidFunc = () => true;
function f2(): void {
return true;
}Üben · Aufgabe 3 von 7
type Listener = (event: string) => ____;
const seen: string[] = [];
const listeners: Listener[] = [(e) => console.log("got " + e), (e) => seen.push(e)];
listeners.forEach((listener) => listener("save"));
console.log(seen.length);Üben · Aufgabe 4 von 7
function keys(o: object): string[] {
return Object.keys(o);
}
function keys2(o: Object): string[] {
return Object.keys(o);
}
console.log(keys({ a: 1 }), keys([5]), keys(() => 1));
console.log(keys2(42));
console.log(keys(42));Üben · Aufgabe 5 von 7
const counter = {
count: 0,
increment(this: { count: number }) {
this.count++;
return this.count;
},
};
console.log(counter.increment());
const inc = counter.increment;
inc();Denksport · Aufgabe 6 von 7
const user = {
name: "Ada",
greet() {
return "Hi, " + this.name;
},
};
const greet = user.greet;
console.log(user.greet());
console.log(greet());Anwenden · Aufgabe 7 von 7
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Das DB aus dem Handbook als Interface: filterUsers ruft seinen Callback mit einem User als this auf, und der Callback ist ein Funktionsausdruck, der this: User deklariert. forEach nimmt einen void-Callback, und die Zahl von push wird ignoriert. safeParse gibt unknown zurück, also prüft das Programm den Wert, bevor es theme liest. countKeys nimmt ein object. Führen Sie npx tsc aus, dann node main.ts. Ersetzen Sie danach den Callback durch () => this.admin und führen Sie tsc erneut aus.
main.ts
interface User {
name: string;
admin: boolean;
}
// filter is called with a User as this: declare it, as the Handbook's DB does.
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const users: User[] = [
{ name: "Ada", admin: true },
{ name: "Grace", admin: false },
{ name: "Linus", admin: true },
];
const db: DB = {
filterUsers(filter) {
return users.filter((user) => filter.call(user));
},
};
// A function expression, not an arrow: an arrow would not get this from the call.
const admins = db.filterUsers(function (this: User) {
return this.admin;
});
// A void callback may return something: push returns a number, which forEach ignores.
const names: string[] = [];
admins.forEach((user) => names.push(user.name));
console.log("admins: " + names.join(", "));
// unknown: the caller must check the value before using it.
function safeParse(s: string): unknown {
return JSON.parse(s);
}
const settings = safeParse('{"theme": "dark"}');
if (typeof settings === "object" && settings !== null && "theme" in settings) {
console.log("theme: " + String(settings.theme));
}
// object accepts arrays and functions too, but no primitives.
function countKeys(value: object): number {
return Object.keys(value).length;
}
console.log(countKeys({ a: 1, b: 2 }), countKeys(["x"]), countKeys(() => 0));
Ausführen mit
npx tsc
node main.tsAusgabe
admins: Ada, Linus
theme: dark
2 1 0Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
Übung 1 von 2
Ein Listener bekommt eine Nachricht. Die beiden Listener unten pushen in history, und push gibt eine Zahl zurück, also meldet tsc für jeden von ihnen error TS2322: Type 'number' is not assignable to type 'undefined'. Ändern Sie Listener so, dass jeder Listener passt, egal was er zurückgibt. Geben Sie on den Rückgabetyp void. Lassen Sie dann emit zurückgeben, wie viele Listener es aufgerufen hat, mit dem Rückgabetyp number.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
Welchen Rückgabetyp verwendet das Handbook für einen Callback, dessen Ergebnis ignoriert wird?
export type Listener = (message: string) => void; dann wird die Zahl von push einfach ignoriert.
In emit nach dem forEach: return listeners.length; und schreiben Sie : number hinter die Parameterliste.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export type Listener = (message: string) => void;
const listeners: Listener[] = [];
export const history: string[] = [];
export function on(listener: Listener): void {
listeners.push(listener);
}
export function emit(message: string): number {
listeners.forEach((listener) => listener(message));
return listeners.length;
}
on((message) => history.push(message));
on((message) => history.push(message.toUpperCase()));
Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.ts
export type Listener = (message: string) => undefined;
const listeners: Listener[] = [];
export const history: string[] = [];
export function on(listener: Listener) {
listeners.push(listener);
}
export function emit(message: string) {
listeners.forEach((listener) => listener(message));
}
on((message) => history.push(message));
on((message) => history.push(message.toUpperCase()));
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {on, emit, history} from './main.ts';
test('emit ruft beide Listener auf und gibt 2 zurück', () => {
const called = emit('hi');
assert.equal(called, 2, `emit('hi') gab ${called} zurück`);
});
test('die Listener haben die Nachricht in history geschrieben', () => {
assert.deepEqual(history.slice(0, 2), ['hi', 'HI'], `history beginnt mit ${JSON.stringify(history.slice(0, 2))}`);
});
test('ein später hinzugefügter Listener wird auch aufgerufen', () => {
const got = [];
on((message) => got.push(message));
const called = emit('ok');
assert.equal(called, 3, `nach einem dritten Listener gab emit ${called} zurück`);
assert.deepEqual(got, ['ok'], `der neue Listener bekam ${JSON.stringify(got)}`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.
Programm ausführen:
npx tsc
node main.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladenÜbung 2 von 2
clickTwice löst increment vom Zähler und ruft es allein auf, also ist this undefined, und die Tests brechen mit einem TypeError ab. tsc bemerkt es nicht, weil increment this nicht deklariert. Deklarieren Sie es im Interface Counter: increment(this: Counter): number. Führen Sie die Tests aus: tsc meldet jetzt error TS2684 in clickTwice. Korrigieren Sie clickTwice so, dass es increment zweimal als Methode von counter aufruft und das zweite Ergebnis zurückgibt.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser den TypeScript-Compiler herunter (bis zu 2.1 MB) und speichert ihn im Cache. Ihr Code bleibt auf Ihrem Gerät.
this ist der erste Parameter in der Signatur, und er ist kein echter Parameter: increment(this: Counter): number;.
Das Objektliteral in makeCounter braucht keine Änderung: Es bekommt sein this vom Rückgabetyp Counter.
Rufen Sie die Methode mit ihrem Objekt auf: counter.increment(); dann return counter.increment();.
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
export interface Counter {
count: number;
step: number;
increment(this: Counter): number;
}
export function makeCounter(step: number): Counter {
return {
count: 0,
step,
increment() {
this.count += this.step;
return this.count;
},
};
}
export function clickTwice(counter: Counter): number {
counter.increment();
return counter.increment();
}
Installieren Sie TypeScript 7.0 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.ts
export interface Counter {
count: number;
step: number;
increment(): number;
}
export function makeCounter(step: number): Counter {
return {
count: 0,
step,
increment() {
this.count += this.step;
return this.count;
},
};
}
export function clickTwice(counter: Counter): number {
const click = counter.increment;
click();
return click();
}
main.test.ts
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {makeCounter, clickTwice} from './main.ts';
test('increment addiert die Schrittweite', () => {
const counter = makeCounter(2);
const result = counter.increment();
assert.equal(result, 2, `makeCounter(2).increment() gab ${result} zurück`);
});
test('clickTwice gibt den Stand nach zwei Klicks zurück', () => {
const result = clickTwice(makeCounter(5));
assert.equal(result, 10, `clickTwice(makeCounter(5)) gab ${result} zurück`);
});
test('clickTwice ändert den übergebenen Zähler', () => {
const counter = makeCounter(3);
clickTwice(counter);
assert.equal(counter.count, 6, `nach clickTwice ist count ${counter.count}`);
});
package.json
{
"type": "module"
}
tsconfig.json
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"lib": [
"esnext",
"dom"
],
"types": [],
"strict": true,
"noEmit": true,
"erasableSyntaxOnly": true,
"verbatimModuleSyntax": true,
"allowImportingTsExtensions": true
},
"include": [
"**/*.ts"
],
"exclude": [
"**/*.test.ts"
]
}
npx tsc braucht TypeScript im Ordner: Führen Sie dort einmal npm install --save-dev typescript aus. tsc prüft die Typen; Node.js führt main.ts aus, indem es sie entfernt.
Programm ausführen:
npx tsc
node main.tsPrüfungen ausführen (learnrun.js muss im selben Ordner liegen):
npx tsc
node --testlearnrun.js herunterladenfunction save(name: string): void {
console.log("saved " + name);
return name.length;
}
Was tsc oder Node.js ausgibt
main.ts(3,3): error TS2322: Type 'number' is not assignable to type 'void'.Warum, und die Lösung
Eine Funktion, deren eigener Rückgabetyp void ist, darf keinen Wert zurückgeben; das darf nur eine Funktion in einem void-Funktionstyp. Wenn Aufrufer die Länge brauchen, sagen Sie es: function save(name: string): number. Wenn nicht, löschen Sie die return-Anweisung.
function runLater(f: () => void) {
return f();
}
const r = runLater(() => 42);
console.log(r.toFixed());
Was tsc oder Node.js ausgibt
main.ts(5,15): error TS2339: Property 'toFixed' does not exist on type 'void'.Warum, und die Lösung
f hat den Typ () => void, also gilt alles, was es zurückgibt, als void, auch wenn diese Arrow-Funktion 42 zurückgibt. Genau das bedeutet void: Der Aufrufer verspricht, das Ergebnis nicht zu verwenden. Soll runLater das Ergebnis weitergeben, typisieren Sie den Callback damit: f: () => number, und geben Sie runLater den Rückgabetyp number.
interface User {
name: string;
admin: boolean;
}
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const users: User[] = [
{ name: "Ada", admin: true },
{ name: "Grace", admin: false },
];
const db: DB = {
filterUsers(filter) {
return users.filter((user) => filter.call(user));
},
};
const admins = db.filterUsers(() => this.admin);
Was tsc oder Node.js ausgibt
main.ts(21,37): error TS2532: Object is possibly 'undefined'.Warum, und die Lösung
Eine Arrow-Funktion hat kein eigenes this; sie verwendet das this ihrer Umgebung, und außerhalb jeder Funktion in main.ts gibt es keines. filterUsers setzt this für seinen Callback nur, wenn er ein Funktionsausdruck ist. Das Handbook: „you need to use function and not arrow functions“. Schreiben Sie db.filterUsers(function (this: User) { return this.admin; }).
TypeScript im Browser: der Compiler von TypeScript 6.0.3, Apache-2.0, danach die eigene Engine Ihres Browsers. Lizenz und Quellcode
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.