TypeScript函数与对象的类型增强
一、函数的类型定义:让参数和返回值“有规矩”
在JavaScript中,函数的参数和返回值可以是任意类型,很容易出现“传错值”或“返回不符合预期的结果”的问题。TypeScript通过类型标注,能强制约束函数的参数类型和返回值类型,让函数更“可靠”。
1. 基础语法:参数类型 + 返回值类型
函数的类型定义由两部分组成:
- 括号内:给每个参数标注类型(参数名: 类型)
- 括号后:用:标注返回值类型(如果没有返回值,用void)
例1:简单函数的类型标注
// 函数声明:参数a和b都是number类型,返回值也是number类型
function add(a: number, b: number): number {return a + b;
}// 函数表达式:效果和声明一致,只是写法不同
const multiply = function(x: number, y: number): number {return x * y;
};
为什么要这样做?
如果调用时传入错误类型(比如字符串),TypeScript会直接报错,提前避免运行时问题:
add(1, "2"); // 报错:参数"2"是string,不是number
2. 无返回值的函数:用void标注
如果函数没有return语句,或return后没有值,返回值类型用void(表示“无类型”)。
例2:无返回值的函数
// 打印消息,没有返回值,返回值类型为void
function logMessage(message: string): void {console.log(message);// 即使写return; 也不影响,因为没有返回实际值
}
二、可选参数与默认参数:让函数更灵活
实际开发中,函数的参数可能“可选”(可传可不传),或有“默认值”。TypeScript提供了专门的语法支持。
1. 可选参数:用?标记
在参数名后加?,表示该参数可传可不传。
注意:可选参数必须放在必选参数的后面(否则会报错)。
例3:可选参数的使用
// 必选参数name,可选参数age(可传可不传)
function greet(name: string, age?: number): string {if (age) {return `Hello, ${name}, you are ${age} years old.`;} else {return `Hello, ${name}.`;}
}greet("Alice"); // 正确:只传必选参数
greet("Bob", 25); // 正确:传所有参数
2. 默认参数:参数自带默认值
如果调用时没传该参数,会自动使用默认值。TypeScript会自动推断默认参数的类型,无需额外标注。
例4:默认参数的使用
// 必选参数name,默认参数city(默认值为"Beijing")
function introduce(name: string, city: string = "Beijing"): string {return `${name} is from ${city}.`;
}introduce("Charlie"); // 正确:city自动用"Beijing",返回"Charlie is from Beijing."
introduce("David", "Shanghai"); // 正确:用传入的"Shanghai"
小技巧:默认参数可以像可选参数一样省略,但它的位置可以不严格在最后(不过建议放在后面,更易读)。
三、函数重载:一个函数“适配”多种参数
有时我们需要一个函数能处理“不同类型/数量的参数”,并返回对应类型的结果。比如:
- 传入string类型的参数,返回string;
- 传入number类型的参数,返回number。
这时候可以用函数重载:先定义“重载签名”(描述不同的参数和返回值),再写“实现签名”(实际逻辑)。
1. 重载的语法:先签名,再实现
// 重载签名1:参数为string,返回string
function formatValue(value: string): string;// 重载签名2:参数为number,返回number
function formatValue(value: number): number;// 实现签名:必须兼容所有重载签名(参数类型是string|number,返回值也是string|number)
function formatValue(value: string | number): string | number {if (typeof value === "string") {return value.toUpperCase(); // 字符串转大写} else {return value * 2; // 数字乘2}
}
2. 调用效果
formatValue("hello"); // 正确:返回"HELLO"(string类型)
formatValue(10); // 正确:返回20(number类型)
formatValue(true); // 报错:没有对应的重载签名(参数不能是boolean)
核心原则:实现签名的参数类型和返回值类型,必须“包含”所有重载签名的类型(否则会报错)。
四、对象类型:用接口(Interface)规范结构
JavaScript中对象的结构很灵活,但有时我们需要强制对象“必须有哪些属性”“属性是什么类型”。TypeScript的接口(Interface) 就是用来干这个的:它像一个“模板”,规定了对象的结构。
- 接口的基础使用:定义对象的“必须属性”
用interface关键字定义接口,里面列出对象需要的属性和对应的类型。
例5:基础接口定义
// 定义一个User接口:规定对象必须有name(string)和age(number)属性
interface User {name: string;age: number;
}// 创建符合User接口的对象(必须包含name和age,且类型正确)
const user1: User = {name: "Eve",age: 30
}; // 正确// 错误示例1:缺少age属性
const user2: User = { name: "Frank" }; // 报错:缺少age// 错误示例2:age类型错误(应为number,实际是string)
const user3: User = { name: "Grace", age: "30" }; // 报错:age类型不对
- 接口的补充特性:可选属性与只读属性
实际开发中,对象的属性可能“可选”(不是必须有),或“只读”(创建后不能修改)。接口也支持这些特性。
(1)可选属性:用?标记
interface Book {title: string; // 必选属性author: string; // 必选属性price?: number; // 可选属性:可传可不传
}const book1: Book = { title: "TS Guide", author: "Alice" }; // 正确:没有price也可以
const book2: Book = { title: "JS Guide", author: "Bob", price: 59 }; // 正确:有price
(2)只读属性:用readonly标记
interface Point {readonly x: number; // 只读属性:创建后不能修改readonly y: number;
}const point: Point = { x: 10, y: 20 };
point.x = 30; // 报错:x是只读属性,不能修改
用途:保护对象的核心属性不被意外修改(比如坐标、ID等)。
3. 接口中的方法:定义对象的“函数属性”
对象中可能有方法(函数),接口也能约束方法的参数和返回值类型。
例6:接口中的方法定义
interface Calculator {// 方法add:参数a和b都是number,返回值是numberadd(a: number, b: number): number;// 方法subtract:参数a和b都是number,返回值是numbersubtract(a: number, b: number): number;
}// 创建符合Calculator接口的对象
const simpleCalc: Calculator = {add: (a, b) => a + b,subtract: (a, b) => a - b
};simpleCalc.add(5, 3); // 正确:返回8
五、接口的继承:复用已有结构,减少重复
如果多个接口有相同的属性或方法,可以让一个接口“继承”另一个接口,复用已有定义,避免重复代码。
1. 继承的语法:用extends关键字
// 基础接口:Person(包含name和age)
interface Person {name: string;age: number;
}// 继承Person接口:Student拥有Person的所有属性,再加上自己的grade
interface Student extends Person {grade: number; // 学生特有属性:年级
}// 创建Student类型的对象:必须包含name、age(来自Person)和grade(自己的)
const student1: Student = {name: "Helen",age: 15,grade: 9
}; // 正确
- 多继承:一个接口继承多个接口
接口可以同时继承多个接口,用逗号分隔。
interface A { a: number }
interface B { b: string }// 接口C同时继承A和B,所以必须包含a和b
interface C extends A, B {c: boolean; // 自己的属性
}const obj: C = { a: 1, b: "hello", c: true }; // 正确