3. TypeScript 类型系统
3.1 类型声明
- 作用:对变量或函数形参进⾏类型声明
let a: string //变量a只能存储字符串
let b: number //变量b只能存储数值
let c: boolean //变量c只能存储布尔值
function demo(x:number,y:number):number{
return x + y
}
demo(100,200)
注意:可以写字⾯量类型(少)
let a: '你好' //a的值只能为字符串“你好”
> let b: 100 //b的值只能为数字100
>
> a = '欢迎'//错误:不能将类型“"欢迎"”分配给类型“"你好"”
> b = 200 //错误:不能将类型“200”分配给类型“100
3.2 类型推断
解释:TS 会根据我们的代码,进⾏类型推导
let d = 10 //TypeScript会推断出变量d的类型是数字
d = false //警告:不能将类型“boolean”分配给类型“number”
注意:类型推断不是万能的,⾯对复杂类型时推断容易出问题,所以尽量还是明确的编写类型声明
3.3 类型总览
JavaScript
中的数据类型:
-
string
-
number
-
boolean
-
null
-
undefined
-
bigint
-
symbol
-
object
注意:object包含: Array 、 Function 、 Date 、 Error …
TypeScript
中的数据类型:
-
string
-
number
-
boolean
-
null
-
undefined
-
bigint
-
symbol
-
object(Array 、 Function 、 Date 、 Error …)
-
any
-
unknown
-
never
-
void
-
tuple
-
enum两个⽤于⾃定义类型的⽅式:1.typeinter2.face
注意:在 TypeScript 里进行类型声明时,一般都是用(原始类型)小写(例如:number、string、boolean)
原因:在 TypeScript里有 Number、String、Boolean(开头为大写首字母) 这些内置构造函数,它们可以用来创建对应的包装对象,但很少用
3.4 常用类型与语法
3.4.1 any
-
含义:任意类型。检查把变量类型设为
any
后,TypeScript 就不管这个变量了,怎么用都行,不会报错提醒 -
分类:
- 显式的any:
let a: any
a = 100
a = '你好'
a = false
- 隐式的any:
let b
b = 100
b = '你好'
b = false
注意:any 类型的变量,可以赋值给任意类型的变量
let c:any
> c = 9
> let x: string
> x = c
3.4.2 unknown
-
含义:未知类型
-
适⽤于:起初不确定数据的具体类型,要后期才能确定
-
any和unknow:
-
同:
- 都能充当任意类型的值:
any
和unknown
都能够接收任意类型的值
- 都能充当任意类型的值:
-
let a: any
a = 100
a = '你好'
a = false;
----------------------
let a: unknow
a = 100
a = '你好'
a = false
* 都能绕过部分类型检查:若把变量类型设为`any`或者`unknown`,TypeScript 就不会对该变量进行类型检查(但 `unknown` 的限制更多)
let valueAny: any = "text";
valueAny.toUpperCase(); // 不报错,直接调用方法
let valueUnknown: unknown = "text";
valueUnknown.toUpperCase(); // 报错:必须先确定类型
-
异:
- 类型安全程度不一样:any 就像直接关掉了类型检查,用它写代码,编辑器不会提醒类型错误。unknown 更谨慎些,被叫做 “安全版 any”,在没确定它具体是什么类型前,你不能随便对它进行操作
let a: any;
a= 42;
a.split(','); // 不报错(运行时可能出错)
------------------------------------------------------
let b: unknown;
b= "hello";
b.split(','); // 报错:类型 "unknown" 上不存在属性 "split"ab
// 正确方式:先类型断言
(bas string).split(','); // 断言第一种方法后可用
(<string>b).sbplit(','); // 断言第二种方法后可用
* 赋值规则有差异:any 特别 “随意”,任何类型的值都能存进去,它里面的值也能直接赋给其他任意类型的变量。unknown 虽然能接收任何类型的值,但它的值只能再赋给 unknown 或者 any 类型的变量
let a: any = 42;
let num: number = a; // 允许(任何类型都能接收 any)
-----------------------------------------
let b: unknown = 42;
let num2: number = b; // 报错:不能将 unknown 赋给其他类型
let b: unknown = b; // 只能赋给 unknown 或 any
* 操作限制不同:用 any 时,你可以直接对它的值做各种操作,不用做额外处理。但 unknown 不行,想对它操作,得先通过类型断言或类型守卫,确定它到底是什么类型才行
function printLength(value: any) {
console.log(value.length); // 不报错(直接操作 any)
}
function printLengthSafe(value: unknown) {
// 直接操作报错
// console.log(value.length);
// 正确方式:先类型守卫
if (typeof value === 'string') {
console.log(value.length); // 此时 value 被收窄为 string
}
// 或使用类型断言
console.log((value as string).length);
}
3.4.3 never
含义:任何值都不是,即:不能有值(undefined 、 null 、 ‘’ 、 0 都不⾏)
注意:⼏乎不⽤ never 去直接限制变量,因为没有意义
3.4.4 void
- 含义:空。即:主要用于表示某个函数没有返回值,调⽤者也不应依赖其返回值进⾏任何操作
function logMessage(a: string): void {
console.log(a);
// 没有返回值,或者隐式返回 undefined
}
void
与undefined
的比较:
特性 | void | undefined |
---|---|---|
语义 | 没有返回值(强调不应该有值) | 值不存在 |
常见用途 | 函数返回值类型 | 变量类型、检查值是否未定义 |
可赋值的类型 | void、any(非严格模式) | 任何类型(非严格模式) |
严格模式限制 | 只能赋值为 undefined(或 null,如果关闭 strictNullChecks) | 只能赋值给 unknown、any、undefined、null |
function returnUndefined(): undefined {
return undefined; // 必须显式返回 undefined
}
function returnVoid(): void {
// 可以不返回任何内容,或者返回 undefined
return; // 隐式返回 undefined
}
注意:
在大多数情形下,函数不返回值时应优先选用
void
,而非undefined
启用
--strictNullChecks
后,void
类型的变量只能被赋值为undefined
3.4.5 object
3.4.5.1 object(小写)类型
-
定义:所有非原始类型的集合
-
范围:
-
对象字面量(如
{}
) -
数组(如
[1,2,3]
) -
函数(如
() => void
) -
类实例(如
new Date()
) -
包装对象(如
new String('test')
)
-
不包含:原始类型:
number
、string
、boolean
、null
、undefined
、symbol
-
例子
let a: object;
a = {}; // 合法
a = [1, 2]; // 合法
a = function() {}; //合法
a = 123; // 错误:不能将number赋值给object
3.4.5.2 Object(大写)类型
-
定义:所有可调用 Object 方法的值的类型
-
范围:
- 所有非
null
/undefined
的值 - 原始类型会被自动装箱为对应的包装对象(如
1
装箱为Number
实例)
-
不包含:
null
和undefined
-
例子:
let b: Object;
b = 123; // 合法(装箱为Number对象)
b = 'test'; // 合法(装箱为String对象)
b = null; // 错误
3.4.5.3 对象类型声明
- 明确指定对象的结构
// 方式1:使用逗号分隔
let person1: { name: string, age?: number };
// 方式2:使用分号分隔(等效)
let person2: { name: string; age?: number };
// 方式3:多行格式(更清晰)
let person3: {
name: string;
age?: number; // 可选属性
};
// 合法赋值
person1 = { name: 'Alice', age: 30 };
person2 = { name: 'Bob' }; // 可选属性可以省略
// 不合法赋值(多余属性)
person3 = { name: 'Charlie', gender: 'male' }; // 错误
- 索引签名(动态属性):描述具有不确定属性名的对象
let dynamicObj: {
name: string;
age?: number;
[key: string]: any; // 允许任意字符串键,值类型为 any
};
// 合法赋值
dynamicObj = {
name: 'Alice',
age: 30,
gender: 'female', // 动态属性
hobby: ['reading', 'swimming'] // 任意类型
};
注意:使用
[key: string]: any
会关闭类型检查,尽量明确属性名和类型
3.4.5.4 函数类型声明
- 箭头函数语法
// 声明函数类型:接收两个 number,返回 number
let add: (a: number, b: number) => number;
// 实现函数
add = function(x, y) {
return x + y;
};
- 接口定义(更复杂场景)
interface Calculator {
(a: number, b: number): number;
}
let subtract: Calculator = (x, y) => x - y;
3.4.5.5 数组类型声明
- 泛型语法(推荐)
let numbers: Array<number> = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];
- 简化语法
let numbers: number[] = [1, 2, 3];
let strings: string[] = ['a', 'b', 'c'];
场景 | 推荐语法 | 示例 |
---|---|---|
对象类型(固定结构) | { 属性1: 类型, 属性2?: 类型 } | let p: { name: string, age?: number } |
对象类型(动态属性) | { [key: string]: 类型 } | let obj: { [key: string]: number } |
函数类型 | (参数: 类型) => 返回类型类型[] 或 Array<类型> | let fn: (a: number) => string |
数组类型 | 类型[] 或 Array<类型> | let arr: number[] 或 Array<number> |
3.4.6 tuple
-
定义:元组是 TS 里特殊的数组类型,它能存固定数量元素,而且每个元素类型固定,还能不一样。
-
例子:
- 解释:arr1这个元组里,第一个元素必须是字符串类型,第二个必须是数字类型
let arr1: [string, number]
//例子:arr1 = ['hello', 123]
- 解释:arr2可以只放一个数字,也可以放一个数字加一个布尔值
let arr2: [number, boolean?]//这里的?意味着第二个元素是可选的
//例子:arr2 = [100, false]或arr2 = [200]
- 解释:第一个元素得是数字,后面能跟着任意数量的字符串。也可以只有第一个数字元素
let arr3: [number, ...string[]]
//例子:arr3 = [100, 'hello', 'world']或arr3 = [100]
3.4.7 enum
- 定义:是一种定义命名常量集合的方式,它能增强代码的可读性,也让代码更好维护
3.4.7.1 数字枚举(默认自增)
enum Direction {
Up,
Down,
Left,
Right
}
//成员值自动递增
console.log(Direction.Up); // 输出: 0
console.log(Direction.Down); // 输出: 1
console.log(Direction.Left); // 输出: 2
console.log(Direction.Right); // 输出: 3
//反向映射
console.log(Direction[0]); // 输出: 'Up'
console.log(Direction[1]); // 输出: 'Down'
3.4.7.2 自定义初始值的数字枚举
enum Direction {
Up = 10,
Down,
Left = 20,
Right
}
console.log(Direction.Up); // 输出: 10
console.log(Direction.Down); // 输出: 11
console.log(Direction.Left); // 输出: 20
console.log(Direction.Right); // 输出: 21
3.4.7.3 字符串枚举(每个成员必须赋值)
enum Direction {
Up = "up",
Down = "down",
Left = "left",
Right = "right"
}
let dir: Direction = Direction.Up;
console.log(dir); // 输出: "up"
3.4.7.4 常量枚举(编译时被内联)
-
概念:使用 const 关键字定义,在编译时会被内联(将枚举成员引用替换为它们的实际值),避免生成一些额外的代码
- 普通枚举:
enum Directions {
Up,
Down,
Left,
Right
}
let x = Directions.Up;
//从ts编译成js后
"use strict";
var Directions;
(function (Directions) {
Directions[Directions["Up"] = 0] = "Up";
Directions[Directions["Down"] = 1] = "Down";
Directions[Directions["Left"] = 2] = "Left";
Directions[Directions["Right"] = 3] = "Right";
})(Directions || (Directions = {}));
let x = Directions.Up;
- 常量枚举:
const enum Directions {
Up,
Down,
Left,
Right
}
let x = Directions.Up;
//从ts编译成js后
"use strict";
let x = 0 /* Directions.Up */;
3.4.8 type
定义:type 可以为任意类型创建别名
3.4.8.1 基本用法
type ID = string | number; // ID可以是字符串或数字
type User = {
id: ID; // 使用上面定义的ID类型
name: string;
age?: number; // 可选属性
};
const user: User = {
id: 123,
name: "Alice",
// age可以省略
};
3.4.8.2 联合类型
type StatusCode = 200 | 404 | 500; // 字面量联合
type Response = string | { message: string }; // 类型联合
function handleResponse(res: Response) {
if (typeof res === "string") {
console.log(res.toUpperCase()); // 处理字符串响应
} else {
console.log(res.message); // 处理对象响应
}
}
3.4.8.3 交叉类型
type Person = {
name: string;
age: number
};
type Employee = {
id: number;
department: string
};
type Staff = Person & Employee; // 同时拥有Person和Employee的所有属性
const staff: Staff = {
name: "Bob",
age: 30,
id: 1001,
department: "IT"
};
3.4.9 一个特殊情况
- 函数直接声明返回值为 void:此时,只能返回
undefined
或不返回值
function demo(): void {
return undefined; // 合法
// return 100; // 错误:不能返回非void类型
}
- 用类型声明限制返回值为 void:当通过类型别名定义函数时,实际函数可以返回任意值,但返回值会被忽略,并且也不可以用这个返回值做任何事
type LogFunc = () => void;
const f: LogFunc = () => {
return 100; // 允许,但返回值会被忽略
};
设计原因:兼容常见 API
Array.forEach
接受返回值为void
的回调,但允许回调中调用有返回值的函数:
> const src = [1, 2, 3];
> const dst = [0];
>
> // forEach期望回调返回void,但push返回number
> src.forEach(el => dst.push(el)); // 正常工作,返回值被忽略
超详细解释:
forEach
的回调函数里调用了push
方法,push
会返回新数组的长度(是一个数字类型的值),但forEach
方法期望的回调函数返回类型是void
。如果 TypeScript 严格要求回调函数必须返回void
,那么在回调函数中调用push
这样有返回值的函数就会报错,这会导致很多常见的代码模式无法正常工作。所以,TypeScript 对于用类型声明限制返回值为void
的情况(如type LogFunc = () => void
),在回调函数中不严格要求必须返回空值,以保证像forEach
结合其他有返回值函数的代码能够正常运行
3.4.10 复习类相关知识
- 类的基本概念:类是一种用户自定义的数据类型,它封装了数据(属性)和操作这些数据的函数(方法)。通过类,我们可以创建多个具有相同结构的对象
class Person {
// 属性声明
name: string
age: number
// 构造器
constructor(name: string, age: number) {
this.name = name // this指向当前创建的对象
this.age = age
}
// ⽅法
speak() {
console.log(`我叫:${this.name},今年${this.age}岁`)
}
}
// 创建Person类的实例p1
const p1 = new Person('周杰伦', 38)
- 继承:继承是面向对象编程的重要特性,允许一个类(子类)继承另一个类(父类)的属性和方法,从而实现代码复用
class Student extends Person {
grade: string //新增属性:年级
// 构造器
constructor(name: string, age: number, grade: string) {
super(name, age) //调用父类的构造器
this.grade = grade //初始化子类新增的属性
}
// 备注本例中若Student类不需要额外的属性,Student的构造器可以省略
// 重写从⽗类继承的⽅法
override speak() {
console.log(`我是学⽣,我叫:${this.name},今年${this.age}岁,在读${this.grade}
年级`,)
}
// ⼦类⾃⼰的⽅法
study() {
console.log(`${this.name}正在努⼒学习中......`)
}
}
3.4.11 属性修饰符
修饰符 | 类内部 | 子类 | 类外部 | 是否可修改 |
---|---|---|---|---|
public | 可 | 可 | 可 | 可 |
protected | 可 | 可 | 不 | 可 |
private | 可 | 不 | 不 | 可 |
readonly | 可 | 可 | 可 | 不 |
3.4.11.1 公开属性(public)
class Public {
public age: number;
constructor(age: number) {
this.age= 10;
}
public publicMethod() {
this.age= 20; // 在类内部可以修改
console.log(`在类内部访问 public 属性:${this.age}`);
}
}
class Student extends Public {
public subMethod() {
this.age= 30; // 在子类中可以修改
console.log(`在子类中访问 public 属性:${this.age}`);
}
}
const p1= new Public();
p1.age= 40; // 在类外部可以修改
console.log(`在类外部访问 public 属性: ${p1.age}`);
p1.publicMethod();
const p2= new Student();
p2.subMethod();
简写形式:
class Public {
constructor(public age: number = 10) {}
publicMethod() {
this.age = 20;
console.log(`类内部: ${this.age}`);
}
}
class Student extends Public {
subMethod() {
this.age = 30;
console.log(`子类中: ${this.age}`);
}
}
const p1 = new Public();
p1.age = 40;
console.log(`类外部: ${p1.age}`);
p1.publicMethod();
const p2 = new Student();
p2.subMethod();
3.4.11.2 受保护属性(protected)
class Public {
protected age: number;
constructor(age: number) {
this.age= 5;
}
protected protectedMethod() {
this.age= 15; // 在类内部可以修改
console.log(`在类内部访问 protected 属性: ${this.age}`);
}
}
class Student extends Public {
public subMethod() {
this.age= 25; // 在子类中可以修改
console.log(`在子类中访问 protected 属性: ${this.age}`);
}
}
const p1= new Public();
// p1.age; // 报错,在类外部不能访问
// p1.protectedMethod(); // 报错,在类外部不能访问
const p2= new Student();
p2.subMethod();
简写形式:
class Public {
constructor(protected age: number = 5) {}
protected protectedMethod() {
this.age = 15;
console.log(`类内部: ${this.age}`);
}
}
class Student extends Public {
public subMethod() {
this.age = 25;
console.log(`子类中: ${this.age}`);
}
}
const p1 = new Public();
// p1.age; // 报错,外部无法访问 protected 属性
// p1.protectedMethod(); // 报错,外部无法调用 protected 方法
const p2 = new Student();
p2.subMethod();
3.4.11.3 私有属性(private)
class Public {
private age: number;
constructor(age: number) {
this.age= 1;
}
private privateMethod() {
this.age= 2; // 在类内部可以修改
console.log(`在类内部访问 private 属性: ${this.age}`);
}
}
class Student extends Public {
// 子类不能访问 private 属性和方法
// age; // 报错,子类不能访问
// privateMethod(); // 报错,子类不能访问
}
const p1= new Public ();
// p1.age; // 报错,在类外部不能访问
// p1.privateMethod(); // 报错,在类外部不能访问
简写形式:
class Public {
constructor(private age: number = 1) {}
private privateMethod() {
this.age = 2;
console.log(`类内部: ${this.age}`);
}
}
class Student extends Public {
// 子类无法访问 private 成员
}
const p1 = new Public();
// p1.age; // 报错:外部无法访问 private 属性
// p1.privateMethod(); // 报错:外部无法调用 private 方法
只读属性(readonly)
class Public {
public readonly age: number;
constructor(age: number) {
this.age = 100;
}
public displayProperty() {
console.log(`在类内部访问 readonly 属性: ${this.age}`);
}
}
class Student extends Public {
public subDisplay() {
console.log(`在子类中访问 readonly 属性: ${this.age}`);
}
}
const p1= new Public ();
console.log(`在类外部访问 readonly 属性: ${p1.age}`);
p1.displayProperty();
const p2= new Student ();
p2.subDisplay();
// p1.age= 200; // 报错,readonly 属性不能修改
简写形式:
class Public {
constructor(public readonly age: number = 100) {}
public displayProperty() {
console.log(`在类内部访问 readonly 属性: ${this.age}`);
}
}
class Student extends Public {
public subDisplay() {
console.log(`在子类中访问 readonly 属性: ${this.age}`);
}
}
const p1 = new Public();
console.log(`在类外部访问 readonly 属性: ${p1.age}`);
p1.displayProperty();
const p2 = new Student();
p2.subDisplay();
// p1.age = 200; // 报错,readonly 属性不能修改
3.4.12 抽象类
-
定义:抽象类是一种特殊的类,它不能直接创建对象,就像一个模板,专门用来定义类的结构和行为。抽象类里既可以有抽象方法(只有声明,没有具体实现),也可以有普通方法(有具体实现)
-
主要作用:为子类提供一个基础框架,子类必须实现抽象类里的抽象方法
-
使用场景:
-
为一组相关的类定义通用的行为
-
提供一些基础实现,让子类继承
-
确保子类必须实现某些关键行为
-
避免多个类之间的代码重复
-
-
例子:
- 简单:
// 定义一个抽象类 Animal
abstract class Animal {
constructor(public name: string) {}
// 抽象方法:所有动物都会叫,但叫声不同
abstract makeSound(): void;
// 普通方法:所有动物都会睡觉
sleep(): void {
console.log(`${this.name}正在睡觉`);
}
}
// 子类 Dog 继承自 Animal
class Dog extends Animal {
constructor(name: string) {super(name)}//子类构造函数中调用父类构造函数的方式
// 实现抽象方法
makeSound(): void {
console.log(`${this.name}说:汪汪汪!`);
}
}
// 子类 Cat 继承自 Animal
class Cat extends Animal {
constructor(name: string) {super(name)}//子类构造函数中调用父类构造函数的方式
// 实现抽象方法
makeSound(): void {
console.log(`${this.name}说:喵喵喵!`);
}
}
// 创建实例并调用方法
const dog = new Dog("旺财");
dog.makeSound(); // 输出:旺财说:汪汪汪!
dog.sleep(); // 输出:旺财正在睡觉
const cat = new Cat("咪咪");
cat.makeSound(); // 输出:咪咪说:喵喵喵!
cat.sleep(); // 输出:咪咪正在睡觉
`dog`的执行步骤:
* 步骤一:创建 `dog` 实例
1. `new Dog("旺财")` 调用 `Dog` 构造函数
2. `super("旺财")` 调用父类 `Animal` 的构造函数
3. 父类构造函数初始化 `this.name = "旺财"`
4. `dog` 对象创建完成,包含:
* `name: "旺财"`
* 继承的方法 `sleep()`
* 重写的方法 `makeSound()`
* 步骤 2:调用 `dog.makeSound()`
1. JavaScript 引擎查找 `dog` 对象的 `makeSound()` 方法
2. 找到 `Dog` 类中实现的版本:
console.log(`${this.name}说:汪汪汪!`); // 输出:旺财说:汪汪汪!
* 步骤 3:调用 `dog.sleep()`
1. 引擎查找 `dog` 对象的 `sleep()` 方法(未找到)
2. 沿原型链向上查找,找到 `Animal.prototype.sleep()`
3. 执行父类的 `sleep()` 方法:
console.log(`${this.name}正在睡觉`); // this.name → "旺财"
- 复杂(计算图形面积):
// 定义抽象类 Shape
abstract class Shape {
constructor(public name: string) { }
// 抽象方法:计算面积
abstract calculateArea(): number;
// 普通方法:打印面积
printArea(): void {
console.log(`${this.name}的面积是:${this.calculateArea()}`);
}
}
// 子类 Circle 继承自 Shape
class Circle extends Shape {
constructor(
public name: string,
public radius: number
) {super(name)}
// 实现抽象方法
calculateArea(): number {
return Math.PI * this.radius * this.radius;
}
}
// 子类 Rectangle 继承自 Shape
class Rectangle extends Shape {
constructor(
public name: string,
public width: number,
public height: number
) {super(name)}
// 实现抽象方法
calculateArea(): number {
return this.width * this.height;
}
}
// 创建实例并调用方法
const circle = new Circle("圆形", 5);
circle.printArea(); // 输出:圆形的面积是:78.5398163397448
const rectangle = new Rectangle("矩形", 4, 6);
rectangle.printArea(); // 输出:矩形的面积是:24
3.4.13 interface(接口)
-
定义:接口(interface)是 TypeScript 中用来定义数据结构的工具,它就像是一份契约,规定了类、对象或函数必须遵循的格式,但不包含具体实现
-
使用场景:
-
对象格式定义:描述数据模型、API 响应、配置对象等
-
类的契约:强制类实现特定属性和方法
-
扩展已有接口:扩展第三方库的类型定义(如在大型项目中)
-
3.4.13.1 定义类的结构
接口可以约束类的属性和方法,确保类符合特定的格式
// 定义接口:规定Person类必须有name、age属性和speak方法
interface PersonInterface {
name: string;
age: number;
speak(n: number): void; // 方法签名(只定义参数和返回类型)
}
// 类实现接口
class Person implements PersonInterface {
constructor(
public name: string, // 自动创建同名属性
public age: number
) {}
// 必须实现接口中的speak方法
speak(n: number): void {
for (let i = 0; i < n; i++) {
console.log(`你好,我叫${this.name},今年${this.age}岁`);
}
}
}
// 创建实例并调用方法
const p1 = new Person("小明", 20);
p1.speak(2); // 输出两次问候语
3.4.13.2 定义对象的结构
接口可以描述对象的形状,包括属性类型、可选属性和只读属性
interface UserInterface {
name: string;
readonly gender: string; // 只读属性(初始化后不可修改)
age?: number; // 可选属性(可以不存在)
run: (distance: number) => void; // 方法类型
}
// 创建符合接口的对象
const user: UserInterface = {
name: "张三",
gender: "男", // 必须初始化,且之后不能修改
age: 18, // 可选属性可以省略
run(distance) {
console.log(`我跑了${distance}米`);
},
};
user.age = 20; // 允许修改可选属性
// user.gender = "女"; // 错误:只读属性不可修改
3.4.13.3 定义函数的结构
接口可以描述函数的参数和返回值类型
// 定义函数接口
interface Calculator {
(a: number, b: number): number; // 函数签名
}
// 实现接口的函数
const add: Calculator = (x, y) => {
return x + y;
};
const result = add(3, 5); // result类型为number
3.4.13.4 接口的继承
接口可以继承其他接口,复用已有结构并扩展新属性
// 基础接口
interface Person {
name: string;
age: number;
}
// 继承并扩展
interface Student extends Person {
grade: string; // 新增属性
}
// 创建符合Student接口的对象
const stu: Student = {
name: "李四",
age: 25,
grade: "高三", // 必须包含扩展的属性
};
3.4.13.5 接口的自动合并(同名接口)
多个同名接口会自动合并为一个接口
// 第一次定义接口
interface Config {
host: string;
port: number;
}
// 第二次定义同名接口(自动合并)
interface Config {
timeout: number; // 新增属性
}
// 使用合并后的接口
const config: Config = {
host: "localhost",
port: 8080,
timeout: 5000, // 必须包含所有合并的属性
};
3.4.14 相似概念区别
3.4.14.1 interface 与 type
特性 | Interface | type |
---|---|---|
定义对象结构 | 可以定义对象、类的结构 | 可以定义对象结构 |
继承 / 扩展 | 支持通过extends 继承其他接口 | 可以通过交叉类型(& )实现类似继承的效果 |
声明合并 | 同名接口会自动合并 | 不能重复定义,会报错 |
联合类型 | 无法直接定义联合类型 | 可以定义联合类型,如 string \| number |
交叉类型 | 不能直接定义交叉类型 | 可以通过& 定义交叉类型 |
基本类型别名 | 不能定义基本类型的别名 | 可以定义基本类型别名,如: type ID = string \| number |
映射类型 | 无法直接参与映射类型 | 可以参与映射类型,如: type Readonly<T> = { readonly [P in keyof T]: T[P] } |
实现类 | 类可以实现(implements )接口 | 类可以实现由 type 定义的对象类型 |
扩展内置类型 | 不能扩展内置类型(如 Array) | 可以通过交叉类型扩展内置类型 |
语法形式 | 使用 interface 关键字 | 使用type 关键字 |
- 定义对象结构
// interface 方式
interface UserInterface {
name: string;
age: number;
}
// type 方式
type UserType = {
name: string;
age: number;
};
// 使用方式完全相同
const user1: UserInterface = { name: 'Alice', age: 30 };
const user2: UserType = { name: 'Bob', age: 25 };
- 继承 / 扩展
// interface 继承
interface Animal {
name: string;
}
interface Dog extends Animal {
bark(): void;
}
// type 交叉类型实现类似继承
type AnimalType = { name: string };
type DogType = AnimalType & { bark(): void };
- 联合类型
// interface 无法直接定义联合类型
// type 可以
type Status = 'success' | 'error' | 'pending';
- 声明合并
// interface 支持声明合并
interface Config {
port: number;
}
interface Config {
host: string; // 合并后 Config 包含 port 和 host
}
// type 不支持重复定义
type Settings = { theme: string };
// type Settings = { mode: string }; // 报错:重复定义
-
实现类
/
```typescript
/ interface 实现类
interface Shape {
area(): number;
}
class Circle implements Shape {
constructor(public radius: number) {}
area() { return Math.PI * this.radius ** 2; }
}
// type 实现类
type ShapeType = { area(): number };
class Square implements ShapeType {
constructor(public side: number) {}
area() { return this.side ** 2; }
}
-
使用场景:
-
当需要定义对象或类的结构,并且可能需要通过继承扩展时优先使用 interface
-
当需要定义联合类型、交叉类型、基本类型别名,或处理更复杂的类型操作时使用 type
-
两者并非互斥,可以根据场景灵活搭配使用
-
3.4.14.2 interface 与抽象类
-
相同点:接口和抽象类都能定义一个类的格式,也就是规定类应该遵循的契约
-
不同点:
- 接口(Interface):
// 定义 FlyInterface 接口
interface FlyInterface {
fly(): void;
}
// 定义 SwimInterface 接口
interface SwimInterface {
swim(): void;
}
// Duck 类实现了 FlyInterface 和 SwimInterface 两个接口
class Duck implements FlyInterface, SwimInterface {
fly(): void {
console.log('鸭子可以飞');
}
swim(): void {
console.log('鸭子可以游泳');
}
}
// 创建一个 Duck 实例
const duck = new Duck();
duck.fly(); // 输出: 鸭子可以飞
duck.swim(); // 输出: 鸭子可以游泳
* 只能描述结构,不能有任何实现代码
* 一个类可以实现多个接口
- 抽象类:
// 定义一个抽象类 Animal
abstract class Animal {
// 抽象方法,子类必须实现
abstract makeSound(): void;
// 具体方法,子类可以直接使用
move(): void {
console.log('动物在移动');
}
}
// Dog 类继承自 Animal 抽象类
class Dog extends Animal {
makeSound(): void {
console.log('汪汪汪');
}
}
// 创建一个 Dog 实例
const dog = new Dog();
dog.makeSound(); // 输出: 汪汪汪
dog.move(); // 输出: 动物在移动
* 既可以包含抽象方法(只有声明,没有实现),也可以包含具体方法(有实现代码)
* 一个类只能继承一个抽象类
特性 | 接口 (Interface ) | 抽象类 |
---|---|---|
定义 | 只定义方法签名,不能包含方法实现 | 可以包含抽象方法(无实现)和具体方法(有实现) |
实现方式 | 使用 implements 关键字,可实现多个接口 | 使用 extends 关键字,只能继承一个抽象类 |
构造函数 | 不能有构造函数 | 可以有构造函数 |
访问修饰符 | 所有方法默认是 public,不能使用其他修饰符 | 可以使用 public 、protected 等修饰符 |
属性 | 可以定义只读属性(使用 readonly ) | 可以定义各种属性 |
用途 | 用于定义契约,实现多继承特性 | 用于代码复用和部分实现的共享 |