第3章 控制流与函数
本章定位:深入学习TypeScript中函数的声明与使用,包括普通函数、箭头函数、泛型函数,以及参数类型注解、返回值类型、默认参数、可选参数等。
3.1 函数声明与箭头函数
Section titled “3.1 函数声明与箭头函数”TypeScript的函数声明与Python的def类似,但语法不同:
// TypeScript函数声明function greet(name: string): string { return `Hello, ${name}!`;}
// 调用const message = greet("Alice");console.log(message); // "Hello, Alice!"Python对比:
def greet(name: str) -> str: return f"Hello, {name}!"
# 调用message = greet("Alice")print(message) # "Hello, Alice!"函数声明的类型
Section titled “函数声明的类型”// 函数声明可以带有类型注解function add(a: number, b: number): number { return a + b;}
// 函数本身也有类型const myAdd: (x: number, y: number) => number = add;箭头函数是ES6引入的语法,类似Python的lambda,但更强大:
// 普通函数function square(n: number): number { return n * n;}
// 箭头函数const squareArrow = (n: number): number => n * n;
// 单参数可以省略括号const double = n => n * 2; // 不推荐,清晰度差
// 无参数箭头函数const getRandom = () => Math.random();
// 多行箭头函数需要花括号const calculate = (x: number, y: number): number => { const temp = x * 2; return temp + y;};Python对比:
# 普通函数def square(n): return n * n
# lambda(箭头函数的Python等价)square_lambda = lambda n: n * n
# 多行需要用普通函数def calculate(x, y): temp = x * 2 return temp + ythis的绑定差异
Section titled “this的绑定差异”普通函数中的this:
const person = { name: "Alice", greet: function() { // this指向person对象 console.log(`Hello, ${this.name}`); }, greetDelayed: function() { setTimeout(function() { // this在普通函数中指向window/undefined(在严格模式下) console.log(`Hello, ${this.name}`); // undefined! }, 1000); }};箭头函数中的this:
const person = { name: "Alice", greet: function() { // 箭头函数捕获外层的this console.log(`Hello, ${this.name}`); }, greetDelayed: function() { setTimeout(() => { // 箭头函数不重新绑定this console.log(`Hello, ${this.name}`); // "Hello, Alice" }, 1000); }};Python无this概念,对应的是self:
class Person: def __init__(self, name): self.name = name
def greet(self): print(f"Hello, {self.name}")
def greet_delayed(self): import time # Python threading 不改变 self 引用 print(f"Hello, {self.name}")函数声明 vs 箭头函数的选择
Section titled “函数声明 vs 箭头函数的选择”| 场景 | 推荐 | 原因 |
|---|---|---|
| 对象方法 | 普通函数 | 需要正确的this绑定 |
| 回调函数 | 箭头函数 | 保持外部this |
| 高阶函数参数 | 箭头函数 | 简洁 |
| 需要递归 | 普通函数 | 箭头函数不能被引用 |
| 需要动态this | 普通函数 | 箭头函数绑定外层this |
3.2 参数类型与返回值类型
Section titled “3.2 参数类型与返回值类型”基础参数类型
Section titled “基础参数类型”// 单个参数function greet(name: string): string { return `Hello, ${name}!`;}
// 多个参数function add(a: number, b: number): number { return a + b;}
// 参数类型错误add("1", "2"); // 编译错误!参数类型不匹配TypeScript可以推断返回值类型,但显式标注更清晰:
// TypeScript推断返回numberfunction add(a: number, b: number) { return a + b;}
// 显式标注返回stringfunction formatName(first: string, last: string): string { return `${last}, ${first}`;}
// 返回void(不返回任何值)function logMessage(message: string): void { console.log(message);}
// 返回never(永不返回)function throwError(message: string): never { throw new Error(message);}
// 永不返回的另一种方式function infiniteLoop(): never { while (true) { // 无限循环 }}推断 vs 显式标注
Section titled “推断 vs 显式标注”// 推断(简洁,适合简单函数)const add = (a: number, b: number) => a + b;
// 显式标注(清晰,适合复杂函数)const calculate = (data: number[]): number => { const sum = data.reduce((acc, n) => acc + n, 0); return sum / data.length;};
// void返回的推断const log = (msg: string) => console.log(msg);// 普通函数function createUser(name: string, age: number) { return { name, age };}
// 构造函数function User(name: string, age: number) { this.name = name; this.age = age;}User.prototype.greet = function() { return `Hello, I'm ${this.name}`;};
// 使用new调用const user = new User("Alice", 30);console.log(user.greet()); // "Hello, I'm Alice"Python对比:
class User: def __init__(self, name: str, age: int): self.name = name self.age = age
def greet(self) -> str: return f"Hello, I'm {self.name}"3.3 可选参数与默认参数
Section titled “3.3 可选参数与默认参数”使用?标记可选参数:
// name是必需的,greeting是可选的function greet(name: string, greeting?: string): string { if (greeting) { return `${greeting}, ${name}!`; } return `Hello, ${name}!`;}
greet("Alice"); // "Hello, Alice!"greet("Alice", "Hi"); // "Hi, Alice!"
// 可选参数必须在必需参数之后function greet(greeting: string, name?: string): string { // 这是合法的}
// 错误示例:可选参数不能在必需参数之前// function greet(name?: string, greeting: string) // Error!Python对比:
def greet(name: str, greeting: str | None = None) -> str: if greeting: return f"{greeting}, {name}!" return f"Hello, {name}!"
greet("Alice") # "Hello, Alice!"greet("Alice", "Hi") # "Hi, Alice!"使用=设置默认值:
function createUser( name: string, age: number = 18, role: string = "user"): User { return { name, age, role };}
createUser("Alice"); // {name: "Alice", age: 18, role: "user"}createUser("Bob", 25); // {name: "Bob", age: 25, role: "user"}createUser("Charlie", 30, "admin"); // {name: "Charlie", age: 30, role: "admin"}Python对比:
def create_user(name: str, age: int = 18, role: str = "user") -> dict: return {"name": name, "age": age, "role": role}
create_user("Alice") # {"name": "Alice", "age": 18, "role": "user"}create_user("Bob", 25) # {"name": "Bob", "age": 25, "role": "user"}默认参数与类型推断
Section titled “默认参数与类型推断”// 默认参数让TypeScript推断可选function logMessage(message: string, level: string = "info"): void { console.log(`[${level}] ${message}`);}
// level可以是string | undefinedlogMessage("Hello"); // OKlogMessage("Hello", "error"); // OKlogMessage("Hello", undefined); // OK,但没必要默认参数与undefined
Section titled “默认参数与undefined”function greet(name: string, greeting: string = "Hello"): string { return `${greeting}, ${name}!`;}
// 显式传递undefined会使用默认值greet("Alice", undefined); // "Hello, Alice!"
// 显式传递undefined vs 不传greet("Alice"); // "Hello, Alice!"greet("Alice", undefined); // "Hello, Alice!"(效果相同)3.4 剩余参数(rest parameters)
Section titled “3.4 剩余参数(rest parameters)”使用...语法收集剩余参数为数组:
// 收集所有额外参数为数组function sum(...numbers: number[]): number { return numbers.reduce((acc, n) => acc + n, 0);}
sum(1, 2, 3); // 6sum(1, 2, 3, 4, 5); // 15sum(); // 0
// 普通参数 + 剩余参数function logMessage(level: string, ...messages: string[]): void { messages.forEach(msg => console.log(`[${level}] ${msg}`));}
logMessage("info", "Hello", "World", "!");// [info] Hello// [info] World// [info] !Python对比:
# Python使用*args收集剩余参数def sum(*numbers): return sum(numbers)
sum(1, 2, 3) # 6sum(1, 2, 3, 4, 5) # 15
def log_message(level, *messages): for msg in messages: print(f"[{level}] {msg}")
log_message("info", "Hello", "World", "!")剩余参数的类型
Section titled “剩余参数的类型”// number数组function collect(...args: number[]): number[] { return args;}
// string数组function join(...parts: string[]): string { return parts.join("-");}
// 混合类型(联合类型)function mixed(...args: (string | number)[]): (string | number)[] { return args;}
// 对象数组function createUsers(...users: { name: string; age: number }[]): User[] { return users;}剩余参数的限制
Section titled “剩余参数的限制”-
只能有一个剩余参数
// 错误:多个剩余参数// function f(...a: number[], ...b: string[]) {} -
剩余参数必须在最后
// 错误:剩余参数不在最后// function f(...args: number[], extra: string) {} -
剩余参数不能有默认值
// 错误:剩余参数不能有默认值// function f(...args: number[] = []) {}
解构剩余参数
Section titled “解构剩余参数”// 接收对象解构function createUser({ name, age, role = "user" }: { name: string; age: number; role?: string;}): User { return { name, age, role };}
// 使用展开运算符传入对象const userData = { name: "Alice", age: 30 };createUser(userData); // OKPython对比:
def create_user(name: str, age: int, role: str = "user") -> dict: return {"name": name, "age": age, "role": role}
# Python解包user_data = {"name": "Alice", "age": 30}create_user(**user_data) # OK3.5 函数重载:多态的实现方式
Section titled “3.5 函数重载:多态的实现方式”TypeScript支持函数重载,允许同一函数名有多个函数签名:
// 函数重载签名function reverse(str: string): string;function reverse(arr: string[]): string[];function reverse(strOrArr: string | string[]): string | string[] { if (typeof strOrArr === "string") { return strOrArr.split("").reverse().join(""); } else { return strOrArr.reverse(); }}
reverse("hello"); // "olleh"reverse(["a", "b", "c"]); // ["c", "b", "a"]reverse(123); // 编译错误!重载签名规则
Section titled “重载签名规则”- 两个或更多的重载签名
- 最后一个签名是实现签名
- 实现签名参数类型必须是所有重载签名的超类型
// 错误示例:实现签名不是超类型// function f(x: string): void;// function f(x: number): void;// function f(x: string | number): void; // OK实际应用场景
Section titled “实际应用场景”处理不同输入类型:
// 多种参数组合interface Point { x: number; y: number;}
function createPoint(): Point;function createPoint(x: number): Point;function createPoint(x: number, y: number): Point;function createPoint(x?: number, y?: number): Point { return { x: x ?? 0, y: y ?? 0 };}
createPoint(); // {x: 0, y: 0}createPoint(10); // {x: 10, y: 0}createPoint(10, 20); // {x: 10, y: 20}工厂模式:
class Animal { constructor(public name: string) {}}
class Dog extends Animal { constructor(name: string, public breed: string) { super(name); }}
class Cat extends Animal { constructor(name: string, public color: string) { super(name); }}
function createAnimal(type: "dog", name: string, breed: string): Dog;function createAnimal(type: "cat", name: string, color: string): Cat;function createAnimal(type: "dog" | "cat", name: string, extra: string): Animal { if (type === "dog") { return new Dog(name, extra); } else { return new Cat(name, extra); }}
const dog = createAnimal("dog", "Buddy", "Golden");const cat = createAnimal("cat", "Whiskers", "Orange");重载与联合类型的对比
Section titled “重载与联合类型的对比”// 使用联合类型function process(input: string | string[]): string | string[] { if (typeof input === "string") { return input.toUpperCase(); } return input.map(s => s.toUpperCase());}
// 使用重载function processString(input: string): string;function processString(input: string[]): string[];function processString(input: string | string[]): string | string[] { if (typeof input === "string") { return input.toUpperCase(); } return input.map(s => s.toUpperCase());}
// 重载更明确,IDE提示更好// 联合类型更简洁,适合简单场景3.6 泛型函数:类型参数的使用
Section titled “3.6 泛型函数:类型参数的使用”泛型允许函数操作多种类型而不丢失类型信息:
// 泛型函数:T是类型参数function identity<T>(arg: T): T { return arg;}
// 使用时指定类型const num = identity<number>(42); // numberconst str = identity<string>("hello"); // string
// 让TypeScript推断类型const num2 = identity(42); // numberconst str2 = identity("hello"); // stringPython对比:
from typing import TypeVar
T = TypeVar("T")
def identity(x: T) -> T: return x
num = identity(42) # intstr = identity("hello") # str多个类型参数
Section titled “多个类型参数”// 多个泛型参数function pair<K, V>(key: K, value: V): [K, V] { return [key, value];}
const p1 = pair<string, number>("age", 30); // [string, number]const p2 = pair("name", "Alice"); // [string, string],推断使用extends限制类型参数的范围:
// 要求T必须有length属性function logLength<T extends { length: number }>(arg: T): number { return arg.length;}
logLength("hello"); // 5logLength([1, 2, 3]); // 3logLength({ length: 10 }); // 10logLength(123); // 编译错误!number没有length
// 约束T必须是某种类型function getProperty<T extends object, K extends keyof T>( obj: T, key: K): T[K] { return obj[key];}
const user = { name: "Alice", age: 30 };const name = getProperty(user, "name"); // stringconst age = getProperty(user, "age"); // numbergetProperty(user, "email"); // 编译错误!"email"不在user中Python对比:
from typing import TypeVar
T = TypeVar("T")
def get_property(obj: dict, key: str): return obj[key]
# Python无法在编译时检查key是否有效user = {"name": "Alice", "age": 30}name = get_property(user, "name") # "Alice"age = get_property(user, "age") # 30email = get_property(user, "email") # 运行时才报错泛型函数与数组
Section titled “泛型函数与数组”// 泛型数组操作function firstElement<T>(arr: T[]): T | undefined { return arr[0];}
const nums = firstElement([1, 2, 3]); // number | undefinedconst strs = firstElement(["a", "b"]); // string | undefined
// 约束为number数组function sum<T extends number>(arr: T[]): number { return arr.reduce((acc, n) => acc + n, 0);}
sum([1, 2, 3]); // 6sum(["a", "b"]); // 编译错误!string不是number的子类型泛型回调函数
Section titled “泛型回调函数”// 泛型 + 回调函数function map<T, U>(arr: T[], fn: (item: T) => U): U[] { return arr.map(fn);}
const numbers = [1, 2, 3];const squared = map(numbers, n => n * n); // number[]const strings = map(numbers, n => `Number: ${n}`); // string[]
// Promise泛型async function fetchData<T>(url: string): Promise<T> { const response = await fetch(url); return response.json() as T;}
const user = await fetchData<User>("https://api.example.com/user");泛型默认类型
Section titled “泛型默认类型”TypeScript 2.3+ 支持泛型默认类型:
// 默认类型参数function createArray<T = string>(length: number, value: T): T[] { return Array(length).fill(value);}
const strArr = createArray(3); // string[](使用默认)const numArr = createArray(3, 42); // number[]const boolArr = createArray<boolean>(3, true); // boolean[]3.7 Python def vs TypeScript function
Section titled “3.7 Python def vs TypeScript function”| Python | TypeScript | 说明 |
|---|---|---|
def f(x): | function f(x: type): | 函数声明 |
-> str | : string | 返回类型标注位置 |
lambda x: x | (x: type) => x | 箭头函数 |
*args | ...args: type[] | 剩余参数 |
**kwargs | {...}: { key: type } | 命名参数 |
def f(x=5) | function f(x: number = 5) | 默认参数 |
函数定义的详细对比
Section titled “函数定义的详细对比”# Python:基础函数def add(a: int, b: int) -> int: return a + b
# TypeScriptconst add = (a: number, b: number): number => a + b;// 或function add(a: number, b: number): number { return a + b;}# Python:可选参数def greet(name: str, greeting: str = "Hello") -> str: return f"{greeting}, {name}!"
# TypeScriptconst greet = (name: string, greeting: string = "Hello"): string => `${greeting}, ${name}!`;# Python:可变参数def sum(*args: int) -> int: total = 0 for n in args: total += n return total
# TypeScriptconst sum = (...args: number[]): number => args.reduce((acc, n) => acc + n, 0);# Python:lambdasquare = lambda x: x ** 2# 或def square(x): return x ** 2
# TypeScriptconst square = (x: number): number => x ** 2;# Python:类型约束(有限)from typing import TypeVar
T = TypeVar("T", int, float)
def double(x: T) -> T: return x * 2
# TypeScriptfunction double<T extends number | string>(x: T): T { return x as any * 2; // 需要类型断言}调用约定对比
Section titled “调用约定对比”# Python:位置参数和关键字参数def create_user(name, age, role="user"): return {"name": name, "age": age, "role": role}
create_user("Alice", 30) # 位置create_user("Bob", 25, role="admin") # 混合create_user(age=35, name="Charlie") # 关键字(顺序无关)// TypeScript:位置参数function createUser(name: string, age: number, role: string = "user"): User { return { name, age, role };}
createUser("Alice", 30); // 位置createUser("Bob", 25, "admin"); // 位置// createUser({ name: "Charlie", age: 35 }); // 不支持关键字参数// 但可以用对象解构function createUser({ name, age, role = "user" }: { name: string; age: number; role?: string;}): User { return { name, age, role };}createUser({ name: "Alice", age: 30 }); // 类似关键字参数# Python:可以返回多个值def get_stats(numbers): return len(numbers), sum(numbers), min(numbers), max(numbers)
count, total, min_val, max_val = get_stats([1, 2, 3, 4, 5])// TypeScript:返回元组实现多值function getStats(numbers: number[]): [number, number, number, number] { return [ numbers.length, numbers.reduce((a, b) => a + b, 0), Math.min(...numbers), Math.max(...numbers) ];}
const [count, total, minVal, maxVal] = getStats([1, 2, 3, 4, 5]);参数类型检查对比
Section titled “参数类型检查对比”# Python:type hint仅供阅读,不强制def add(a: int, b: int) -> int: return a + b
add("1", "2") # 运行时报错:TypeError// TypeScript:编译时检查const add = (a: number, b: number): number => a + b;
add("1", "2"); // 编译错误!类型不匹配高阶函数对比
Section titled “高阶函数对比”# Python:函数作为参数和返回值def apply_twice(func, x): return func(func(x))
def add_five(x): return x + 5
result = apply_twice(add_five, 10) # 20
# 返回函数def create_multiplier(factor): def multiplier(x): return x * factor return multiplier
double = create_multiplier(2)triple = create_multiplier(3)print(double(5)) # 10print(triple(5)) # 15// TypeScript:函数作为参数和返回值function applyTwice<T>(func: (x: T) => T, x: T): T { return func(func(x));}
const addFive = (x: number): number => x + 5;const result = applyTwice(addFive, 10); // 20
// 返回函数function createMultiplier(factor: number): (x: number) => number { return (x: number) => x * factor;}
const double = createMultiplier(2);const triple = createMultiplier(3);console.log(double(5)); // 10console.log(triple(5)); // 153.8 本章小结
Section titled “3.8 本章小结”本章学习了TypeScript的函数与控制流:
-
函数声明与箭头函数:
- 普通函数使用
function关键字,箭头函数使用=> - 箭头函数不绑定自己的
this - 箭头函数不能用作构造函数
- 普通函数使用
-
参数与返回值类型:
- 参数类型注解在参数名后
- 返回值类型注解在参数列表后
- 可以省略,让TypeScript推断
-
可选参数与默认参数:
?标记可选参数=设置默认值- 可选参数在必需参数之后
-
剩余参数:
...args: type[]收集额外参数- 只能有一个剩余参数
- 必须在参数列表最后
-
函数重载:
- 多个函数签名,一个实现
- 最后一个签名是实现签名
- 适合处理多种输入类型
-
泛型函数:
<T>定义类型参数extends约束类型参数- 可以有多个类型参数
-
Python对比:
- Python用
def,TypeScript用function或箭头函数 - Python参数顺序更灵活(关键字参数)
- TypeScript编译时检查更严格
- Python用
练习3.1:函数基础
Section titled “练习3.1:函数基础”编写以下函数:
reverseString(str: string): string- 反转字符串isEven(n: number): boolean- 判断偶数
练习3.2:默认参数与可选参数
Section titled “练习3.2:默认参数与可选参数”编写formatDate(date: Date, format?: string): string函数:
- 无format参数时返回”YYYY-MM-DD”
- 有format参数时按format格式化
练习3.3:剩余参数
Section titled “练习3.3:剩余参数”编写merge(...arrays: string[][]): string[]函数,合并多个数组。
练习3.4:函数重载
Section titled “练习3.4:函数重载”编写parse(input: string): Date或parse(input: number): Date,根据输入类型返回不同结果。
练习3.5:泛型函数
Section titled “练习3.5:泛型函数”编写unique<T>(arr: T[]): T[]函数,返回去重后的数组。
练习3.6:对比练习
Section titled “练习3.6:对比练习”用Python和TypeScript分别实现以下功能,比较语法差异:
- 一个计算器函数,接受操作符和两个数字
- 一个高阶函数,接收函数并应用两次
下一章我们将学习TypeScript的复合类型,包括数组、元组、对象、接口、联合类型等,掌握如何构建复杂的数据结构。