【三. 深入解析TypeScript类型系统:从基础到高级】

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 中的数据类型:
  1. string

  2. number

  3. boolean

  4. null

  5. undefined

  6. bigint

  7. symbol

  8. object

注意:object包含: Array 、 Function 、 Date 、 Error …

  • TypeScript 中的数据类型:
  1. string

  2. number

  3. boolean

  4. null

  5. undefined

  6. bigint

  7. symbol

  8. object(Array 、 Function 、 Date 、 Error …)

  9. any

  10. unknown

  11. never

  12. void

  13. tuple

  14. 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:

    • 同:

      • 都能充当任意类型的值:anyunknown都能够接收任意类型的值
      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
  }
  • voidundefined 的比较:
特性voidundefined
语义没有返回值(强调不应该有值)值不存在
常见用途函数返回值类型变量类型、检查值是否未定义
可赋值的类型void、any(非严格模式)任何类型(非严格模式)
严格模式限制只能赋值为 undefined(或 null,如果关闭 strictNullChecks)只能赋值给 unknown、any、undefined、null
     function returnUndefined(): undefined {
       return undefined; // 必须显式返回 undefined
     }
     
     function returnVoid(): void {
       // 可以不返回任何内容,或者返回 undefined
       return; // 隐式返回 undefined
     }

注意:

  1. 在大多数情形下,函数不返回值时应优先选用 void,而非 undefined

  2. 启用 --strictNullChecks 后,void 类型的变量只能被赋值为 undefined

3.4.5 object

3.4.5.1 object(小写)类型
  1. 定义:所有非原始类型的集合

  2. 范围:

  • 对象字面量(如{}

  • 数组(如[1,2,3]

  • 函数(如() => void

  • 类实例(如new Date()

  • 包装对象(如new String('test')

  1. 不包含:原始类型:numberstringbooleannullundefinedsymbol

  2. 例子

  let a: object;
  a = {}; // 合法
  a = [1, 2]; // 合法
  a = function() {}; //合法
  
  a = 123; // 错误:不能将number赋值给object
3.4.5.2 Object(大写)类型
  1. 定义:所有可调用 Object 方法的值的类型

  2. 范围:

  • 所有非null/undefined的值
  • 原始类型会被自动装箱为对应的包装对象(如1装箱为Number实例)
  1. 不包含:nullundefined

  2. 例子:

  let b: Object;
  b = 123; // 合法(装箱为Number对象)
  b = 'test'; // 合法(装箱为String对象)
  
  b = null; // 错误
3.4.5.3 对象类型声明
  1. 明确指定对象的结构
      // 方式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' }; // 错误
  1. 索引签名(动态属性):描述具有不确定属性名的对象
  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 函数类型声明
  1. 箭头函数语法
  // 声明函数类型:接收两个 number,返回 number
  let add: (a: number, b: number) => number;
  
  // 实现函数
  add = function(x, y) {
    return x + y;
  };
  1. 接口定义(更复杂场景)
  interface Calculator {
    (a: number, b: number): number;
  }
  
  let subtract: Calculator = (x, y) => x - y;
3.4.5.5 数组类型声明
  1. 泛型语法(推荐)
  let numbers: Array<number> = [1, 2, 3];
  let strings: Array<string> = ['a', 'b', 'c'];
  1. 简化语法
  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 一个特殊情况

  1. 函数直接声明返回值为 void:此时,只能返回undefined或不返回值
  
  function demo(): void {
    return undefined; // 合法
    // return 100; // 错误:不能返回非void类型
  }
  1. 用类型声明限制返回值为 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
特性Interfacetype
定义对象结构可以定义对象、类的结构可以定义对象结构
继承 / 扩展支持通过extends继承其他接口可以通过交叉类型(&)实现类似继承的效果
声明合并同名接口会自动合并不能重复定义,会报错
联合类型无法直接定义联合类型可以定义联合类型,如 string \| number
交叉类型不能直接定义交叉类型可以通过&定义交叉类型
基本类型别名不能定义基本类型的别名可以定义基本类型别名,如: type ID = string \| number
映射类型无法直接参与映射类型可以参与映射类型,如: type Readonly<T> = { readonly [P in keyof T]: T[P] }
实现类类可以实现(implements)接口类可以实现由 type 定义的对象类型
扩展内置类型不能扩展内置类型(如 Array)可以通过交叉类型扩展内置类型
语法形式使用 interface 关键字使用type关键字
  1. 定义对象结构
 // 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 };
  1. 继承 / 扩展
 // interface 继承
  interface Animal {
    name: string;
  }
  interface Dog extends Animal {
    bark(): void;
  }
  
  // type 交叉类型实现类似继承
  type AnimalType = { name: string };
  type DogType = AnimalType & { bark(): void };
  1. 联合类型
// interface 无法直接定义联合类型
  // type 可以
  type Status = 'success' | 'error' | 'pending';
  1. 声明合并
// interface 支持声明合并
  interface Config {
    port: number;
  }
  interface Config {
    host: string; // 合并后 Config 包含 port 和 host
  }
  
  // type 不支持重复定义
  type Settings = { theme: string };
  // type Settings = { mode: string }; // 报错:重复定义
  1. 实现类

    /


```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,不能使用其他修饰符可以使用 publicprotected 等修饰符
属性可以定义只读属性(使用 readonly可以定义各种属性
用途用于定义契约,实现多继承特性用于代码复用和部分实现的共享
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值