TypeScript 初探:前端开发新手的学习之路

认识TypeScript

什么是TypeScript?

TypeScript是JavaScript的增强版本,引入了静态类型检查和更多面向对象的特性,以提高代码质量和可维护性。

为什么要学TypeScript?

  • 强大的类型系统: 提前捕捉错误,改善代码稳定性。

  • 面向对象编程: 类、接口等特性,提高代码结构清晰度。

  • 协作效率: 类型定义减少团队协作中的误解,提高开发效率。

  • 优秀的工具支持: 集成开发工具,如Visual Studio Code,提升开发体验。

TypeScript和JavaScript的关系

TypeScript是JavaScript的超集,兼容所有JavaScript代码。可逐步迁移项目,享受静态类型检查等优势,编译器将其转换为可在任何支持JavaScript的环境中运行的代码。学习TypeScript提升现代前端开发应对挑战的能力。

搭建开发环境

安装Node.js和npm

  • Node.js安装: 访问 nodejs.org,下载并安装最新版本的Node.js。Node.js自带npm(Node包管理器)。

使用npm安装TypeScript

  • 全局安装TypeScript: 打开终端或命令提示符,运行以下命令安装全局TypeScript:
bash 复制代码
npm install -g typescript

配置TypeScript编译器

  • 创建tsconfig.json文件: 在项目根目录下创建一个tsconfig.json文件,用于配置TypeScript编译器的设置。可以通过以下命令生成:
bash 复制代码
tsc --init
  • 编辑tsconfig.json: 根据项目需求,调整tsconfig.json中的配置,例如指定编译输出目录、调整目标JavaScript版本等。
json 复制代码
{
  "compilerOptions": {
    "target": "es5",
    "outDir": "./dist",
    "strict": true
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules"]
}
  • "target":生成的JavaScript版本。
  • "module":生成的JavaScript模块系统。
  • "outDir":编译后JavaScript文件存放的目录。
  • "strict":启用所有严格类型检查选项。

通过以上步骤,你成功搭建了TypeScript开发环境。接下来,编写你的TypeScript代码,例如在src目录下创建一个main.ts文件:

typescript 复制代码
// src/main.ts
function greet(name: string): string {
  return `Hello, ${name}!`;
}

console.log(greet("TypeScript"));

然后,在终端中运行以下命令编译和执行:

bash 复制代码
tsc          // 编译TypeScript代码
node dist/main.js   // 运行编译后的JavaScript代码

这将输出 Hello, TypeScript!,说明你的TypeScript代码已经成功运行。

基础语法入门

变量和数据类型:

声明变量与赋值

在 TypeScript 中,变量的声明可以使用 letconst 关键字。let 用于声明可变的变量,而 const 用于声明不可变的常量。

typescript 复制代码
let age: number = 25;
const name: string = "John";

基本数据类型

number
typescript 复制代码
let count: number = 42;
string
typescript 复制代码
let message: string = "Hello, TypeScript!";
boolean
typescript 复制代码
let isActive: boolean = true;
any 类型

在某些情况下,我们可能不知道变量的类型,可以使用 any 类型:

typescript 复制代码
let variable: any = 10;
variable = "Hello, TypeScript!";

类型别名

使用 type 关键字定义类型别名

在 TypeScript 中,你可以使用 type 关键字创建类型别名。类型别名是一个已存在类型的新名称,可以用于简化复杂类型的定义。

示例:

typescript 复制代码
// 使用类型别名定义对象类型
type Point = {
    x: number;
    y: number;
};

// 使用类型别名定义函数类型
type MathFunction = (x: number, y: number) => number;

在上述示例中,Point 是一个对象类型的类型别名,表示包含 xy 属性的点。MathFunction 是一个函数类型的类型别名,表示接受两个参数并返回一个数字的函数。

简化复杂类型的定义

类型别名常用于简化复杂类型的定义,提高代码的可读性和维护性。通过为复杂类型起一个清晰的名称,可以更容易理解和使用这些类型。

示例:

typescript 复制代码
// 复杂类型的定义
type User = {
    id: number;
    name: string;
    age: number;
    isAdmin: boolean;
};

type Point3D = {
    x: number;
    y: number;
    z: number;
};

// 使用类型别名简化类型定义
type ComplexType = {
    user: User;
    position: Point3D;
};

在这个示例中,ComplexType 是一个包含 UserPoint3D 的复杂类型的类型别名,使其更易于使用和理解。

类型推断

根据赋值语句自动推断变量类型

TypeScript 具有类型推断的能力,即它可以根据变量的赋值语句自动推断出变量的类型。

示例:

typescript 复制代码
// 类型推断
let age = 25; // TypeScript 推断 age 的类型为 number
let message = "Hello, TypeScript!"; // TypeScript 推断 message 的类型为 string

在这个示例中,变量 age 被赋值为 25,因此 TypeScript 推断它的类型为 number。变量 message 被赋值为字符串,所以 TypeScript 推断它的类型为 string

减少不必要的类型注解

由于 TypeScript 具有类型推断,有时候不必手动注明变量的类型,让 TypeScript 根据上下文自动推断类型可以减少冗长的类型注解。

示例:

typescript 复制代码
// 不必要的类型注解
let numberArray: number[] = [1, 2, 3, 4, 5];

// 简化写法,TypeScript 根据赋值语句推断数组类型
let simplifiedNumberArray = [1, 2, 3, 4, 5];

在这个示例中,变量 numberArray 被手动注明为 number 类型的数组,而变量 simplifiedNumberArray 则根据赋值语句自动推断为相同的类型,减少了不必要的类型注解。

Null 和 Undefined

nullundefined 类型

在 TypeScript 中,nullundefined 是两个特殊的类型,分别表示值为 null 和值为 undefined。它们通常用于表示缺失或不存在的值。

示例:

typescript 复制代码
// null 和 undefined 类型
let nullValue: null = null;
let undefinedValue: undefined = undefined;

// 可赋值给任意类型
let numberValue: number = null;
let stringValue: string = undefined;

在这个示例中,nullValue 的类型被明确指定为 null,同理,undefinedValue 的类型被指定为 undefined

严格空值检查的设置

TypeScript 提供了严格空值检查的选项,可以通过在 tsconfig.json 文件中设置 "strictNullChecks": true 来启用。启用严格空值检查后,变量不能被赋值为 nullundefined,除非显式声明为这两个类型。

示例:

typescript 复制代码
// 启用严格空值检查
// tsconfig.json
// {
//   "compilerOptions": {
//     "strictNullChecks": true
//   }
// }

let nonNullableString: string = "Hello";
// 错误,不能将 null 赋值给非允许的类型
let nullableString: string = null; // Error

通过启用严格空值检查,可以避免一些常见的空值相关错误,提高代码的健壮性和可维护性。

函数和参数:

函数的声明

在 TypeScript 中,可以使用 function 关键字声明函数。函数声明通常包括函数名、参数列表和返回类型的注解。

示例:

typescript 复制代码
// 函数声明
function greet(name: string): string {
    return `Hello, ${name}!`;
}

在这个示例中,greet 是一个函数,接受一个参数 name(类型为 string),并返回一个字符串。

函数重载

TypeScript 支持函数重载,即在一个函数名下定义多个函数类型。

typescript 复制代码
function display(value: string): void;
function display(value: number): void;
function display(value: string | number): void {
    console.log(value);
}

display("Hello"); // 输出: Hello
display(42); // 输出: 42

参数类型注解

在函数声明中,可以为参数添加类型注解,以明确参数的预期类型。

typescript 复制代码
// 参数类型注解
function addNumbers(x: number, y: number): number {
    return x + y;
}

在这个示例中,addNumbers 函数接受两个参数 xy,它们的类型都被注解为 number

可选参数和默认参数

在函数声明中,可以使用问号 ? 来表示可选参数,以及使用 = 来指定默认参数值。

typescript 复制代码
// 可选参数和默认参数
function buildName(firstName: string, lastName?: string): string {
    if (lastName) {
        return `${firstName} ${lastName}`;
    } else {
        return firstName;
    }
}

function sayHello(message: string = "Hello"): void {
    console.log(message);
}

在这个示例中,buildName 函数有一个可选参数 lastName,而 sayHello 函数有一个默认参数 message

剩余参数(Rest Parameters)

使用剩余参数语法(以三个点 ... 开头)可以将参数集合到一个数组中。

typescript 复制代码
function mergeWords(...words: string[]): string {
    return words.join(" ");
}

console.log(mergeWords("Hello", "TypeScript", "World")); // 输出: Hello TypeScript World

接口初探:

什么是接口?

在 TypeScript 中,接口是一种用于定义对象形状的抽象类型。接口定义了对象应该具有的属性和方法,从而提供了一种对代码进行约束和组织的机制。

示例:

typescript 复制代码
// 接口定义
interface Person {
    name: string;
    age: number;
}

在这个示例中,Person 接口定义了一个对象应该具有的属性:name(字符串类型)和 age(数字类型)。

可选属性
typescript 复制代码
// 接口中的可选属性
interface Car {
    brand: string;
    model: string;
    year?: number; // 可选属性
}

// 使用接口定义对象
let myCar: Car = { brand: "Toyota", model: "Camry" };

在这个示例中,Car 接口中的 year 属性被标记为可选,对象可以包含或不包含该属性。

使用接口

函数参数类型

接口可以用于约束函数的参数类型,确保函数接受符合特定形状的对象作为参数。

示例:

typescript 复制代码
// 使用接口约束函数参数
interface Person {
    name: string;
    age: number;
}

function printPerson(person: Person): void {
    console.log(`Name: ${person.name}, Age: ${person.age}`);
}

// 调用函数
printPerson({ name: "Alice", age: 28 });

在这个示例中,printPerson 函数接受一个参数 person,其类型被约束为 Person 接口,确保传入的对象具有 nameage 属性。

对象形状的约束

接口还可以用于约束对象的形状,确保对象包含特定的属性和方法。

示例:

typescript 复制代码
// 使用接口约束对象形状
interface Point {
    x: number;
    y: number;
}

// 使用接口定义对象
let point: Point = { x: 10, y: 20 };

在这个示例中,Point 接口约束了对象的形状,确保对象具有 xy 属性。

元组

元组是特殊数组,可包含不同类型元素

元组是 TypeScript 中的一种特殊数组类型,它允许包含不同类型的元素。与数组不同的是,元组中的每个位置都可以具有指定的类型。

示例:

typescript 复制代码
// 元组的声明和初始化
let person: [string, number, boolean];
person = ["Alice", 28, false];

在这个示例中,person 是一个包含字符串、数字和布尔值的元组。

元组类型的声明

可以使用类型注解为元组指定类型,这样在初始化时必须按照指定类型的顺序提供元素。

示例:

typescript 复制代码
// 带类型注解的元组
let employee: [string, number];
employee = ["Bob", 30];

在这个示例中,employee 是一个带有类型注解的元组,包含一个字符串和一个数字。

元组的使用允许你在处理异构数据(不同类型的数据)时更有结构化的方式,但需要小心确保元组的长度和类型与声明一致。

类型断言

类型断言的两种语法

类型断言是一种告诉编译器某个值的类型的方式,有两种语法形式:尖括号语法和 as 语法。

示例:

typescript 复制代码
// 尖括号语法
let value1: any = "Hello, TypeScript!";
let length1: number = (<string>value1).length;

// as 语法
let value2: any = "Hello, TypeScript!";
let length2: number = (value2 as string).length;

在这个示例中,value1value2 的类型被声明为 any,然后使用类型断言将其转换为字符串类型,从而获取字符串的长度。

处理类型不确定的情况

在处理类型不确定的情况下,类型断言可以用于告诉编译器你对值的类型有更好的了解,并且希望使用该类型的属性或方法。

示例:

typescript 复制代码
// 处理类型不确定的情况
function getLength(value: any): number {
    // 使用类型断言获取字符串长度
    return (value as string).length;
}

// 调用函数
let result: number = getLength("Hello, TypeScript!");

在这个示例中,getLength 函数接受一个类型不确定的参数 value,使用类型断言将其转换为字符串类型,然后获取字符串的长度。

类型断言的使用要慎重,确保你对值的类型有足够的了解,以避免运行时错误。

枚举

什么是枚举?

枚举是一种用于定义一组有逻辑关联的常数值的方式,使代码更具可读性和可维护性。枚举成员具有数字或字符串值,用于表示相关的命名常数。

示例:

typescript 复制代码
// 枚举定义
enum Color {
    Red,
    Green,
    Blue,
}

// 使用枚举
let myColor: Color = Color.Green;

在这个示例中,Color 枚举定义了三个成员:RedGreenBlue。枚举成员的值默认为从 0 开始的递增数字。

赋值和手动指定成员的值

可以手动指定枚举成员的值,也可以让 TypeScript 根据默认规则自动赋值。

示例:

typescript 复制代码
// 默认赋值
enum Direction1 {
    North, // 0
    South, // 1
    East,  // 2
    West,  // 3
}

// 手动指定成员的值
enum Direction2 {
    North = 1,  // 1
    South = 2,  // 2
    East = 4,   // 4
    West = 8,   // 8
}

在这个示例中,Direction1 枚举成员的值由默认规则赋值,而 Direction2 枚举成员的值被手动指定。

字符串枚举

字符串枚举是一种枚举类型,其成员使用字符串值而不是默认的数字值。字符串枚举提供了更具可读性的方式来表示一组相关的命名常数。

示例:

typescript 复制代码
// 字符串枚举
enum Direction {
    North = "N",
    South = "S",
    East = "E",
    West = "W",
}

// 使用字符串枚举
let myDirection: Direction = Direction.North;

在这个示例中,Direction 枚举的成员使用字符串值,例如 North 的值为 "N"。字符串枚举增加了可读性,使得代码更加清晰。

面向对象编程

类和对象

类的定义与实例化

在 TypeScript 中,类是一种将属性和方法组合到一起的机制。通过 class 关键字,我们可以定义一个类。类中包含了属性和方法的声明,这些方法可以操作类的属性。

typescript 复制代码
class Animal {
    // 属性
    name: string;

    // 构造函数
    constructor(name: string) {
        this.name = name;
    }

    // 方法
    makeSound(): void {
        console.log("Some generic sound");
    }
}

// 创建类的实例
const dog = new Animal("Dog");
dog.makeSound(); // 输出: Some generic sound

在这个例子中,Animal 类有一个属性 name 和一个方法 makeSound。通过 new Animal("Dog") 创建了一个 Animal 类的实例,并通过 dog.makeSound() 调用了它的方法。

访问修饰符

访问修饰符用于控制类成员的可访问性。在 TypeScript 中,有三种访问修饰符:publicprivateprotected

  • public(默认): 公共成员,可以在类内部和外部访问。
typescript 复制代码
class Person {
    name: string;

    constructor(name: string) {
        this.name = name;
    }
}

const person = new Person("John");
console.log(person.name); // 合法,输出: John
  • private: 私有成员,只能在类内部访问。
typescript 复制代码
class Person {
    private age: number;

    constructor(age: number) {
        this.age = age;
    }

    getAge(): number {
        return this.age; // 合法,在类内部访问私有成员
    }
}

const person = new Person(25);
console.log(person.getAge()); // 合法,通过公共方法访问私有成员
// console.log(person.age); // 错误,不能在外部直接访问私有成员
  • protected: 受保护的成员,可以在类内部和继承的子类中访问。
typescript 复制代码
class Animal {
    protected category: string;

    constructor(category: string) {
        this.category = category;
    }
}

class Dog extends Animal {
    constructor(category: string) {
        super(category);
    }

    displayCategory(): void {
        console.log(`Category: ${this.category}`); // 合法,在子类中访问受保护的成员
    }
}

const dog = new Dog("Mammal");
dog.displayCategory(); // 输出: Category: Mammal
// console.log(dog.category); // 错误,不能在外部直接访问受保护的成员

继承和多态

继承

继承是面向对象编程中的一种重要概念,它允许一个类(子类)继承另一个类(父类)的属性和方法。通过继承,子类可以复用父类的代码,同时可以添加或重写父类的行为。

typescript 复制代码
class Animal {
    // 属性
    name: string;

    // 构造函数
    constructor(name: string) {
        this.name = name;
    }

    // 方法
    makeSound(): void {
        console.log("Some generic sound");
    }
}

// Dog 类继承自 Animal 类
class Dog extends Animal {
    // 子类可以拥有自己的属性和方法
    bark(): void {
        console.log("Woof! Woof!");
    }
}

// 创建 Dog 类的实例
const myDog = new Dog("Buddy");

// 调用父类的方法
myDog.makeSound(); // 继承父类方法,输出: Some generic sound

// 调用子类的方法
myDog.bark(); // 调用子类方法,输出: Woof! Woof!

在这个例子中,Dog 类继承了 Animal 类的属性和方法,同时添加了自己的方法 bark

多态

多态性允许使用一个基类类型的变量来引用一个派生类的对象,实现对不同对象的统一操作。在 TypeScript 中,多态性通常通过方法的重写来实现。

typescript 复制代码
class Cat extends Animal {
    // 重写父类方法
    makeSound(): void {
        console.log("Meow!");
    }
}

// 多态性的应用
const myCat: Animal = new Cat();
myCat.makeSound(); // 多态调用,输出: Meow!

在这个例子中,Cat 类继承了 Animal 类,并重写了父类的 makeSound 方法。通过将 Cat 类的实例赋值给 Animal 类型的变量 myCat,实现了多态性的应用。即使变量的类型是基类,但在运行时会根据实际对象的类型调用相应的方法。

方法重写 是指子类重新定义或修改了从父类继承的方法。在上述例子中,Cat 类重写了 Animal 类的 makeSound 方法,以适应猫的特有声音。

继承和多态是面向对象编程中的两个关键概念,通过它们可以建立更加灵活、可扩展的代码结构。

抽象类和接口

抽象类

抽象类是一种不能被实例化的类,通常包含一些抽象方法,需要子类实现这些方法。抽象类通过 abstract 关键字定义。

typescript 复制代码
abstract class Shape {
    // 抽象方法,子类必须实现
    abstract draw(): void;

    // 普通方法
    getInfo(): string {
        return "This is a shape.";
    }
}

class Circle extends Shape {
    // 实现抽象方法
    draw(): void {
        console.log("Drawing a circle");
    }
}

const circle = new Circle();
circle.draw(); // 输出: Drawing a circle
console.log(circle.getInfo()); // 输出: This is a shape.

在上述例子中,Shape 类是一个抽象类,包含一个抽象方法 draw 和一个普通方法 getInfoCircle 类继承自 Shape 类,并实现了 draw 方法。

接口

接口是一种用于定义对象形状的抽象结构,包含属性和方法的声明。类可以实现一个或多个接口,实现接口的类必须提供接口中定义的所有属性和方法。

typescript 复制代码
interface Printable {
    print(): void;
}

class Document implements Printable {
    // 实现接口中的方法
    print(): void {
        console.log("Printing document");
    }
}

在这个例子中,Printable 是一个接口,包含一个方法 printDocument 类实现了 Printable 接口,提供了 print 方法的实现。

抽象类和接口的比较

  • 抽象类:

    • 可以包含抽象方法和普通方法。
    • 可以包含成员变量。
    • 通过 extends 关键字继承。
  • 接口:

    • 只能包含抽象方法和属性的声明,不能包含实现。
    • 不能包含成员变量。
    • 通过 implements 关键字实现。
typescript 复制代码
abstract class Animal {
    abstract makeSound(): void;
    abstract move(): void;

    getInfo(): string {
        return "This is an animal.";
    }
}

interface Eatable {
    eat(): void;
}

class Dog extends Animal implements Eatable {
    makeSound(): void {
        console.log("Woof! Woof!");
    }

    move(): void {
        console.log("Running");
    }

    eat(): void {
        console.log("Eating");
    }
}

在上述例子中,Animal 是一个抽象类,包含抽象方法 makeSoundmove,以及一个普通方法 getInfoEatable 是一个接口,包含抽象方法 eatDog 类同时继承自 Animal 类和实现了 Eatable 接口,提供了所有必需的实现。

抽象类和接口是 TypeScript 中用于实现抽象和组合的两个重要概念,可以帮助构建更灵活、可扩展的代码结构。

模块化开发

为什么需要模块化?

模块化开发是一种将代码划分为独立、可重用的模块或文件的编程方法。在大型软件项目中,模块化开发变得至关重要,有以下几个主要原因:

  1. 可维护性: 将代码拆分成模块后,每个模块都有明确的功能和责任,使得代码更易于维护。开发者可以更容易地理解和修改单个模块,而无需深入整个代码库。

  2. 可重用性: 模块化使得代码可以更容易地被重用。一个良好设计的模块可以在多个地方使用,而无需重复编写相同的代码。这降低了开发的工作量,并提高了代码的一致性。

  3. 命名空间与避免命名冲突: 在大型项目中,存在大量的变量和函数。模块化开发通过将这些变量和函数封装在模块中,创建了一个独立的命名空间。这有助于避免全局命名冲突,提高代码的可靠性。

  4. 依赖管理: 模块化开发使得对外部依赖的管理更加清晰。每个模块可以明确指定自己的依赖关系,而不需要关心其他模块的具体实现。这使得项目的结构更加清晰,降低了代码之间的耦合度。

  5. 并行开发: 在模块化开发中,不同的模块可以由不同的开发团队或开发者独立开发。这种并行开发方式提高了项目的开发效率,因为不同的团队可以专注于各自的模块而不会相互干扰。

  6. 测试和调试: 模块化开发简化了测试和调试过程。由于模块具有清晰的边界,可以更容易地对单个模块进行单元测试,而不需要考虑整个应用的复杂性。

综上所述,模块化开发有助于提高代码的可维护性、可重用性,减少命名冲突,简化依赖管理,支持并行开发,以及简化测试和调试过程。这使得大型软件项目更易于管理和扩展。在现代前端开发中,模块化已经成为一种标准实践,被广泛应用于构建可维护、可扩展的应用程序。

导入和导出模块

在 TypeScript 中,使用 importexport 关键字来实现模块的导入和导出。

示例:

typescript 复制代码
// person.ts

export const name: string = "John";
export function sayHello(): void {
    console.log(`Hello, ${name}!`);
}

export class Person {
    constructor(public age: number) {}

    celebrateBirthday(): void {
        console.log(`Happy Birthday! Age: ${this.age}`);
    }
}
typescript 复制代码
// app.ts

// 导入整个模块
import * as PersonModule from "./person";

// 使用导出的内容
console.log(PersonModule.name); // 输出: John
PersonModule.sayHello(); // 输出: Hello, John!

const john = new PersonModule.Person(30);
john.celebrateBirthday(); // 输出: Happy Birthday! Age: 30

通过 import 导入整个模块或通过 { name, sayHello, Person } 导入模块中指定的部分。

使用命名空间

命名空间是一种在全局作用域内组织代码的方式,它将一系列的变量、函数和类封装在一个命名空间中,以避免全局作用域的污染。

示例:

typescript 复制代码
// shapes.ts

namespace Shapes {
    export class Circle {
        constructor(public radius: number) {}

        area(): number {
            return Math.PI * this.radius * this.radius;
        }
    }

    export class Rectangle {
        constructor(public width: number, public height: number) {}

        area(): number {
            return this.width * this.height;
        }
    }
}
typescript 复制代码
// app.ts

// 使用命名空间中的内容
const myCircle = new Shapes.Circle(5);
console.log(myCircle.area()); // 输出: 78.54

const myRectangle = new Shapes.Rectangle(4, 6);
console.log(myRectangle.area()); // 输出: 24

在上述例子中,Shapes 是一个命名空间,包含了 CircleRectangle 两个类。在 app.ts 文件中,通过 Shapes.CircleShapes.Rectangle 使用了命名空间中的内容。

使用命名空间可以有效地组织代码,避免命名冲突,并提高代码的可读性。然而,在现代 TypeScript 中,使用模块更为推荐,因为它提供了更强大的封装和导入导出功能。

TypeScript 高级特性

泛型(Generics)

什么是泛型?

  • 概念和作用:

    • 泛型是 TypeScript 中一种强大的特性,允许在编写函数、类、接口时使用参数化类型。具体来说,可以在定义时使用占位符表示类型,然后在使用时动态指定具体的类型。
    • 作用: 主要用于提高代码的灵活性和重用性,使得函数、类、接口等可以处理多种数据类型而不失去类型检查的优势。
  • 为什么需要使用泛型?

    • 通用性: 泛型使得代码可以同时处理多种类型的数据,而不是针对特定类型写死代码,从而提高代码的通用性。
    • 类型安全: 在使用时指定具体类型,使得在编译阶段就能发现类型错误,提高了代码的类型安全性。
    • 代码重用: 可以编写更灵活、可复用的函数和组件,适应不同类型的数据,避免了代码的冗余。

泛型是 TypeScript 中的一项关键特性,它在让代码更加灵活和通用的同时,保持了类型检查的优势,为开发者提供了一种强大的工具来处理各种数据类型。

泛型函数

  • 创建和使用泛型函数:

    • 在函数名后面使用 <T> 或其他标识符表示泛型参数。

    • 示例:

      typescript 复制代码
      function identity<T>(arg: T): T {
          return arg;
      }
  • 泛型参数和返回值:

    • 泛型参数 <T> 可以用于函数的参数类型和返回值类型。

    • 示例:

      typescript 复制代码
      function pair<T, U>(first: T, second: U): [T, U] {
          return [first, second];
      }
  • 使用泛型函数:

    • 在调用泛型函数时,可以明确指定具体的类型,也可以让 TypeScript 推断类型。

    • 示例:

      typescript 复制代码
      const result1 = identity<string>('Hello');
      const result2 = identity(42); // TypeScript 可以推断出类型为 number
      
      const tupleResult = pair('one', 1); // 类型为 [string, number]

泛型函数允许我们编写与数据类型无关的函数,提高了代码的通用性。通过灵活指定类型参数,可以适应不同类型的输入数据,同时保持了类型安全。

泛型类

  • 在类中使用泛型:

    • 类本身可以是泛型的,也可以在类的方法中使用泛型。

    • 示例:

      typescript 复制代码
      class Box<T> {
          private value: T;
      
          constructor(value: T) {
              this.value = value;
          }
      
          getValue(): T {
              return this.value;
          }
      }
  • 泛型类的应用场景:

    • 容器类: 适用于需要存储或操作不同类型数据的容器,如数组、栈、队列等。

      typescript 复制代码
      const stringBox = new Box<string>('TypeScript');
      const numberBox = new Box<number>(42);
      
      console.log(stringBox.getValue()); // 输出 'TypeScript'
      console.log(numberBox.getValue()); // 输出 42
    • 工具类: 适用于需要提供通用工具方法的类,能够处理不同类型数据的工具逻辑。

      typescript 复制代码
      class MathOperations<T extends number | string> {
          add(a: T, b: T): T {
              if (typeof a === 'number' && typeof b === 'number') {
                  return a + b as T;
              } else if (typeof a === 'string' && typeof b === 'string') {
                  return a + b as T;
              } else {
                  throw new Error('Unsupported types for addition.');
              }
          }
      }
      
      const mathOps = new MathOperations();
      console.log(mathOps.add(5, 3));      // 输出 8
      console.log(mathOps.add('Hello', ' TypeScript'));  // 输出 'Hello TypeScript'

泛型类提供了一种灵活的方式来创建通用的、可复用的类,能够处理多种数据类型,从而提高代码的通用性和可维护性。

泛型约束

  • 如何对泛型进行约束?

    • 使用 extends 关键字约束泛型类型范围,确保泛型类型满足一定的条件。

    • 示例:

      typescript 复制代码
      interface Lengthwise {
          length: number;
      }
      
      function loggingIdentity<T extends Lengthwise>(arg: T): T {
          console.log(arg.length);
          return arg;
      }
  • 泛型约束的实际应用:

    • 确保对象具有特定属性:

      typescript 复制代码
      interface Named {
          name: string;
      }
      
      function displayName<T extends Named>(obj: T): void {
          console.log(obj.name);
      }
    • 确保对象具有特定方法:

      typescript 复制代码
      interface Printable {
          print(): void;
      }
      
      function printObject<T extends Printable>(obj: T): void {
          obj.print();
      }
    • 数组元素的类型约束:

      typescript 复制代码
      function printLength<T extends { length: number }>(arr: T[]): void {
          arr.forEach(item => console.log(item.length));
      }

泛型约束使得我们可以对泛型参数的类型范围进行限制,确保在函数内部能够安全地访问特定的属性或方法。这提高了代码的类型安全性,同时保持了泛型的灵活性。

高级类型(Advanced Types)

联合类型(Union Types)

  • 什么是联合类型?

    • 联合类型是 TypeScript 中一种高级类型,允许变量具有多种可能的类型。

    • 示例:

      typescript 复制代码
      let variable: number | string;
      variable = 42;      // 可以是 number 类型
      variable = 'Hello'; // 也可以是 string 类型
  • 如何处理多种类型的情况?

    • 使用 |(竖线)操作符将多种类型联合在一起。

    • 可以使用条件语句、类型判断等方式在运行时处理不同类型的情况。

    • 示例:

      typescript 复制代码
      function displayType(value: number | string): void {
          if (typeof value === 'number') {
              console.log('It is a number.');
          } else {
              console.log('It is a string.');
          }
      }
      
      displayType(42);      // 输出 'It is a number.'
      displayType('Hello'); // 输出 'It is a string.'

联合类型提供了一种灵活的方式,使得变量可以包含多种类型的值。在处理不同类型的情况时,可以使用类型判断等手段进行更灵活的操作。

交叉类型(Intersection Types)

  • 什么是交叉类型?

    • 交叉类型是 TypeScript 中一种高级类型,允许将多个类型合并为一个类型。

    • 示例:

      typescript 复制代码
      interface Dog {
          bark(): void;
      }
      
      interface Bird {
          fly(): void;
      }
      
      type DogAndBird = Dog & Bird;
  • 如何将多个类型合并为一个类型?

    • 使用 &(与)操作符将多个类型交叉在一起。

    • 合并后的类型将包含所有原始类型的成员。

    • 示例:

      typescript 复制代码
      function petAction(pet: DogAndBird): void {
          pet.bark(); // 具有 Dog 接口的方法
          pet.fly();  // 具有 Bird 接口的方法
      }
      
      const myPet: DogAndBird = {
          bark: () => console.log('Woof!'),
          fly: () => console.log('Flap!'),
      };
      
      petAction(myPet);

交叉类型允许创建具有多种类型成员的新类型,适用于需要合并多个类型特性的场景。在这个例子中,DogAndBird 类型具有同时包含了 DogBird 接口的成员。

条件类型(Conditional Types)

  • 条件类型的语法和使用:

    • 条件类型在 TypeScript 中使用 infer 关键字来引入一种延迟推断的机制。

    • 语法:

      typescript 复制代码
      type MyType<T> = T extends SomeType ? TrueType : FalseType;
    • 示例:

      typescript 复制代码
      type IsString<T> = T extends string ? true : false;
      
      const isString: IsString<number> = false;   // 类型是 false
      const isAnotherString: IsString<string> = true; // 类型是 true
  • 实际场景中的应用示例:

    • 假设我们有一个函数,如果传入的参数是对象,就返回对象的值的类型,否则返回参数本身的类型:

      typescript 复制代码
      type Unbox<T> = T extends { value: infer U } ? U : T;
      
      const stringValue: Unbox<{ value: 'text' }> = 'text'; // 类型是 'text'
      const numberValue: Unbox<number> = 42;              // 类型是 number
    • 在上述例子中,Unbox<T> 条件类型用于检测 T 是否是包含 { value: infer U } 结构的对象,如果是,则返回 U,否则返回 T

条件类型常用于编写更具灵活性的泛型工具,能够根据输入的类型动态地进行类型转换或推断。

映射类型(Mapped Types)

  • 映射类型的基本概念:

    • 映射类型是 TypeScript 中一种强大的工具,用于创建新类型,从而修改或转换现有类型的属性。

    • 使用 in 关键字和 keyof 操作符进行映射。

    • 示例:

      typescript 复制代码
      type Flags = {
          option1: boolean;
          option2: boolean;
      };
      
      type NullableFlags = { [K in keyof Flags]: boolean | null };
  • 如何通过映射类型进行类型转换和修改?

    • 使用 in 关键字和 keyof 操作符,结合泛型和条件类型,可以实现对属性的动态修改和转换。

    • 示例:

      typescript 复制代码
      type UppercaseProps<T> = { [K in keyof T]: T[K] extends string ? Uppercase<T[K]> : T[K] };
      
      interface Person {
          name: string;
          age: number;
          city: string;
      }
      
      type PersonWithUppercaseName = UppercaseProps<Person>;

在这个例子中,UppercaseProps<T> 映射类型用于将传入的类型 T 的字符串属性转换为大写形式。映射类型是 TypeScript 中用于处理现有类型的强大工具,通过动态生成新类型,可以在很大程度上提高代码的灵活性。

其他类型

Never 类型
never 类型的应用场景

never 类型表示永远不会返回结果的表达式,常用于处理异常和不可达代码。

示例:

typescript 复制代码
// 使用 never 处理异常
function throwError(message: string): never {
    throw new Error(message);
}

// 使用 never 处理不可达代码
function infiniteLoop(): never {
    while (true) {
        // 无限循环,不可达代码
    }
}

在这个示例中,throwError 函数抛出异常,其返回类型被标记为 never,因为该函数永远不会正常返回。同样,infiniteLoop 函数是一个无限循环,其返回类型也是 never

类型守卫
使用类型守卫进行类型推断

类型守卫是一种在特定的作用域内判断变量类型的方法,通过它可以缩小变量的类型范围,提高类型安全性。

示例:

typescript 复制代码
// 使用 typeof 类型守卫
function printValue(value: string | number): void {
    if (typeof value === "string") {
        console.log(value.toUpperCase());
    } else {
        console.log(value.toFixed(2));
    }
}

// 使用 instanceof 类型守卫
class Animal {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
}

class Bird extends Animal {
    fly(): void {
        console.log(`${this.name} is flying.`);
    }
}

function printAnimalInfo(animal: Animal): void {
    if (animal instanceof Bird) {
        animal.fly();
    } else {
        console.log(`${animal.name} is not a bird.`);
    }
}

在这个示例中,printValue 函数通过 typeof 类型守卫判断 value 的类型,从而执行不同的操作。而 printAnimalInfo 函数通过 instanceof 类型守卫判断 animal 是否为 Bird 类型,进而执行相应的操作。

类型守卫可以让 TypeScript 在特定情况下更好地理解变量的类型,减少潜在的类型错误。

装饰器(Decorators)

装饰器基础

  • 什么是装饰器?

    • 装饰器是 TypeScript 中一种实验性质的特性,用于添加注释、元数据或修改类和类成员的行为。它借鉴了装饰器模式的概念。
    • 装饰器是一种特殊类型的声明,可附加到类声明、方法、访问器、属性或参数上。
  • 装饰器的基本语法和用途:

    • 类装饰器:

      • 用于在类声明之前被声明。

      • 接受一个参数,即类的构造函数。

      • 示例:

        typescript 复制代码
        function classDecorator(constructor: Function) {
            console.log('Class is decorated!');
        }
        
        @classDecorator
        class ExampleClass {
            // class logic
        }
    • 方法装饰器:

      • 用于声明在方法声明之前被声明。

      • 接受三个参数:类的原型、方法名和方法的属性描述符。

      • 示例:

        typescript 复制代码
        function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            console.log(`Method ${propertyKey} is decorated!`);
        }
        
        class ExampleClass {
            @methodDecorator
            exampleMethod() {
                // method logic
            }
        }
    • 属性装饰器:

      • 用于声明在属性声明之前被声明。

      • 接受两个参数:类的原型和属性名。

      • 示例:

        typescript 复制代码
        function propertyDecorator(target: any, propertyKey: string) {
            console.log(`Property ${propertyKey} is decorated!`);
        }
        
        class ExampleClass {
            @propertyDecorator
            exampleProperty: string;
        }
    • 参数装饰器:

      • 用于声明在参数声明之前被声明。

      • 接受三个参数:类的原型、方法名和参数在函数参数列表中的索引。

      • 示例:

        typescript 复制代码
        function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
            console.log(`Parameter at index ${parameterIndex} is decorated!`);
        }
        
        class ExampleClass {
            exampleMethod(@parameterDecorator param1: string, @parameterDecorator param2: number) {
                // method logic
            }
        }

装饰器是 TypeScript 提供的一种元编程的手段,通过它可以在不修改源代码的情况下对类及其成员进行增强。在实际应用中,装饰器经常与其他元编程特性和设计模式结合使用,提高了代码的可维护性和可扩展性。

类装饰器

  • 创建和应用类装饰器:

    • 创建类装饰器:

      • 类装饰器是一个函数,接收类的构造函数作为参数。

      • 示例:

        typescript 复制代码
        function classDecorator(constructor: Function) {
            console.log('Class is decorated!');
        }
    • 应用类装饰器:

      • 在类声明前使用 @ 符号加上类装饰器。

      • 示例:

        typescript 复制代码
        @classDecorator
        class ExampleClass {
            // class logic
        }
  • 类装饰器的高级应用:

    • 修改类的构造函数:

      • 可以在类装饰器中修改类的构造函数。

      • 示例:

        typescript 复制代码
        function modifyConstructor(constructor: Function) {
            return class extends constructor {
                modifiedProperty = 'Modified!';
            };
        }
        
        @modifyConstructor
        class ExampleClass {
            originalProperty = 'Original!';
        }
        
        const instance = new ExampleClass();
        console.log(instance.originalProperty); // 输出 'Original!'
        console.log(instance.modifiedProperty); // 输出 'Modified!'
    • 装饰器工厂:

      • 可以通过返回函数的方式创建装饰器工厂,实现更灵活的装饰器。

      • 示例:

        typescript 复制代码
        function decoratorFactory(message: string) {
            return function classDecorator(constructor: Function) {
                console.log(`${message}: Class is decorated!`);
            };
        }
        
        @decoratorFactory('Custom Message')
        class ExampleClass {
            // class logic
        }
    • 多个装饰器的执行顺序:

      • 多个装饰器按照从上到下的顺序依次执行。

      • 示例:

        typescript 复制代码
        function firstDecorator(constructor: Function) {
            console.log('First decorator');
        }
        
        function secondDecorator(constructor: Function) {
            console.log('Second decorator');
        }
        
        @firstDecorator
        @secondDecorator
        class ExampleClass {
            // class logic
        }
        // 输出:
        // Second decorator
        // First decorator

类装饰器是一种强大的工具,能够在类声明之前对类进行增强或修改。通过组合多个装饰器,可以实现更丰富的功能,如日志记录、权限控制等。

方法装饰器

  • 创建和应用方法装饰器:

    • 创建方法装饰器:

      • 方法装饰器是一个函数,接收三个参数:类的原型、方法名和方法的属性描述符。

      • 示例:

        typescript 复制代码
        function methodDecorator(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            console.log(`Method ${propertyKey} is decorated!`);
        }
    • 应用方法装饰器:

      • 在方法声明前使用 @ 符号加上方法装饰器。

      • 示例:

        typescript 复制代码
        class ExampleClass {
            @methodDecorator
            exampleMethod() {
                // method logic
            }
        }
  • 方法装饰器的实际应用:

    • 日志记录:

      • 可以使用方法装饰器记录方法的调用信息。

      • 示例:

        typescript 复制代码
        function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            const originalMethod = descriptor.value;
        
            descriptor.value = function (...args: any[]) {
                console.log(`Calling method ${propertyKey} with arguments: ${args.join(', ')}`);
                const result = originalMethod.apply(this, args);
                console.log(`Method ${propertyKey} returned: ${result}`);
                return result;
            };
        
            return descriptor;
        }
        
        class ExampleClass {
            @logMethod
            exampleMethod(message: string) {
                console.log(`Executing method with message: ${message}`);
            }
        }
        
        const instance = new ExampleClass();
        instance.exampleMethod('Hello, TypeScript!');
    • 权限控制:

      • 可以使用方法装饰器实现权限控制逻辑。

      • 示例:

        typescript 复制代码
        function checkPermission(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
            const originalMethod = descriptor.value;
        
            descriptor.value = function (...args: any[]) {
                if (this.hasPermission()) {
                    return originalMethod.apply(this, args);
                } else {
                    console.log('Permission denied!');
                }
            };
        
            return descriptor;
        }
        
        class ExampleClass {
            private hasPermission(): boolean {
                // Check permission logic
                return true;
            }
        
            @checkPermission
            sensitiveOperation() {
                console.log('Executing sensitive operation...');
            }
        }

方法装饰器在实际应用中常用于处理横切关注点,如日志记录、权限控制等。通过在方法执行前或执行后插入逻辑,可以提高代码的可维护性和可拓展性。

属性装饰器

  • 创建和应用属性装饰器:

    • 创建属性装饰器:

      • 属性装饰器是一个函数,接收两个参数:类的原型和属性名。

      • 示例:

        typescript 复制代码
        function propertyDecorator(target: any, propertyKey: string) {
            console.log(`Property ${propertyKey} is decorated!`);
        }
    • 应用属性装饰器:

      • 在属性声明前使用 @ 符号加上属性装饰器。

      • 示例:

        typescript 复制代码
        class ExampleClass {
            @propertyDecorator
            exampleProperty: string;
        }
  • 属性装饰器的使用场景:

    • 属性校验:

      • 可以使用属性装饰器进行属性值的校验。

      • 示例:

        typescript 复制代码
        function validateStringLength(minLength: number, maxLength: number) {
            return function (target: any, propertyKey: string) {
                let value: string;
        
                Object.defineProperty(target, propertyKey, {
                    get: () => value,
                    set: (newValue: string) => {
                        if (newValue.length >= minLength && newValue.length <= maxLength) {
                            value = newValue;
                        } else {
                            console.log(`Invalid length for ${propertyKey}`);
                        }
                    },
                    enumerable: true,
                    configurable: true,
                });
            };
        }
        
        class User {
            @validateStringLength(3, 10)
            username: string = '';
        }
    • 日志记录:

      • 属性装饰器也可用于记录属性的读取和赋值操作。

      • 示例:

        typescript 复制代码
        function logPropertyAccess(target: any, propertyKey: string) {
            let value: any;
        
            Object.defineProperty(target, propertyKey, {
                get: () => {
                    console.log(`Getting value of ${propertyKey}`);
                    return value;
                },
                set: (newValue: any) => {
                    console.log(`Setting value of ${propertyKey} to ${newValue}`);
                    value = newValue;
                },
                enumerable: true,
                configurable: true,
            });
        }
        
        class ExampleClass {
            @logPropertyAccess
            exampleProperty: string = 'Initial Value';
        }

属性装饰器在一些特定场景下非常有用,例如属性值的校验、属性访问的日志记录等。通过修改属性的定义,可以在属性的读取和赋值时插入自定义逻辑。

参数装饰器

  • 创建和应用参数装饰器:

    • 创建参数装饰器:

      • 参数装饰器是一个函数,接收三个参数:类的原型、方法名和参数在函数参数列表中的索引。

      • 示例:

        typescript 复制代码
        function parameterDecorator(target: any, propertyKey: string, parameterIndex: number) {
            console.log(`Parameter at index ${parameterIndex} is decorated!`);
        }
    • 应用参数装饰器:

      • 在方法的参数声明前使用 @ 符号加上参数装饰器。

      • 示例:

        typescript 复制代码
        class ExampleClass {
            exampleMethod(@parameterDecorator param1: string, @parameterDecorator param2: number) {
                // method logic
            }
        }
  • 参数装饰器的使用场景:

    • 参数校验:

      • 参数装饰器可以用于对方法参数的值进行校验。

      • 示例:

        typescript 复制代码
        function validateParameterLength(target: any, propertyKey: string, parameterIndex: number) {
            const originalMethod = target[propertyKey];
        
            target[propertyKey] = function (...args: any[]) {
                const parameterValue = args[parameterIndex];
        
                if (typeof parameterValue === 'string' && parameterValue.length > 5) {
                    console.log(`Parameter at index ${parameterIndex} has valid length`);
                    return originalMethod.apply(this, args);
                } else {
                    console.log(`Invalid length for parameter at index ${parameterIndex}`);
                }
            };
        }
        
        class ExampleClass {
            @validateParameterLength
            exampleMethod(param1: string, param2: string) {
                // method logic
            }
        }
    • 日志记录:

      • 参数装饰器也可以用于记录方法参数的值。

      • 示例:

        typescript 复制代码
        function logParameterValue(target: any, propertyKey: string, parameterIndex: number) {
            const originalMethod = target[propertyKey];
        
            target[propertyKey] = function (...args: any[]) {
                const parameterValue = args[parameterIndex];
                console.log(`Value of parameter at index ${parameterIndex}: ${parameterValue}`);
                return originalMethod.apply(this, args);
            };
        }
        
        class ExampleClass {
            @logParameterValue
            exampleMethod(param1: string, param2: number) {
                // method logic
            }
        }

参数装饰器在一些特定场景下非常有用,例如参数值的校验、参数访问的日志记录等。通过修改方法的定义,可以在方法执行前插入自定义逻辑。

其他高级特性

可辨识联合(Discriminated Unions)

  • 什么是可辨识联合?

    • 可辨识联合是 TypeScript 中一种通过具有共同字段的单例类型来保护联合类型的技术。
    • 它通过在联合类型中引入共同的属性,称为"标签"或"discriminant",来区分不同的成员类型。
  • 如何利用可辨识联合提高代码的安全性?

    • 示例:

      typescript 复制代码
      interface Square {
          kind: 'square';
          size: number;
      }
      
      interface Circle {
          kind: 'circle';
          radius: number;
      }
      
      interface Triangle {
          kind: 'triangle';
          sideLength: number;
      }
      
      type Shape = Square | Circle | Triangle;
      
      function getArea(shape: Shape): number {
          switch (shape.kind) {
              case 'square':
                  return shape.size * shape.size;
              case 'circle':
                  return Math.PI * shape.radius * shape.radius;
              case 'triangle':
                  return (Math.sqrt(3) / 4) * shape.sideLength * shape.sideLength;
              default:
                  // TypeScript 提供了 Exhaustiveness Checking,确保我们处理了所有可能的情况
                  const _exhaustiveCheck: never = shape;
                  return _exhaustiveCheck;
          }
      }
      
      const square: Square = { kind: 'square', size: 5 };
      const circle: Circle = { kind: 'circle', radius: 3 };
      const triangle: Triangle = { kind: 'triangle', sideLength: 4 };
      
      console.log(getArea(square));   // 输出 25
      console.log(getArea(circle));   // 输出 ~28.27
      console.log(getArea(triangle)); // 输出 ~6.93

在上述示例中,Shape 是一个联合类型,每个成员都有一个共同的字段 kind,用于标识具体的类型。在 getArea 函数中,通过使用 switch 语句根据 kind 来处理不同类型的形状,确保了在代码中处理了所有可能的情况,提高了代码的安全性。如果添加了新的形状类型,TypeScript 会提醒我们更新 switch 语句以处理新类型,避免了遗漏的问题。

明确赋值断言(Definite Assignment Assertion)

  • 为什么需要明确赋值断言?

    • TypeScript 引入了"明确赋值断言"(Definite Assignment Assertion)来解决一些特定情况下的赋值问题。
    • 在某些情况下,TypeScript 无法确定变量是否已经被赋值,这可能导致编译错误。
  • 如何在可能为空的情况下使用断言?

    • 示例:

      typescript 复制代码
      let username: string;
      
      if (Math.random() > 0.5) {
          username = 'Alice';
      }
      
      // 此处 TypeScript 会报错,因为 TypeScript 无法确定 username 是否已被赋值
      // const length = username.length; // Error: Object is possibly 'undefined'.
      
      // 在可能为空的情况下使用断言:
      const length = username!.length; // 使用 ! 断言,告诉 TypeScript 我们确信 username 不为空
      
      console.log(length);

在上述示例中,由于 username 的赋值是根据随机条件的,TypeScript 无法确定在后续的代码中 username 是否已被赋值。此时,我们可以使用明确赋值断言(!)告诉 TypeScript 我们确信 username 不为空,从而避免编译错误。

需要注意的是,使用明确赋值断言时,我们需要确保变量确实在使用前被正确赋值,否则可能会导致运行时错误。因此,使用这种断言时要格外小心,确保对代码的了解和控制。

面向对象设计模式在 TypeScript 中的应用

  • 如何使用 TypeScript 实现常见的面向对象设计模式?

    • 单例模式(Singleton Pattern):

      • 保证一个类只有一个实例,并提供一个全局访问点。
      typescript 复制代码
      class Singleton {
          private static instance: Singleton;
      
          private constructor() {}
      
          public static getInstance(): Singleton {
              if (!Singleton.instance) {
                  Singleton.instance = new Singleton();
              }
              return Singleton.instance;
          }
      
          public showMessage(): void {
              console.log("Hello, Singleton!");
          }
      }
      
      const singleton1 = Singleton.getInstance();
      const singleton2 = Singleton.getInstance();
      
      console.log(singleton1 === singleton2); // 输出 true,表示是同一个实例
    • 观察者模式(Observer Pattern):

      • 定义对象间一对多的依赖关系,使得当一个对象改变状态时,所有依赖它的对象都会收到通知并自动更新。
      typescript 复制代码
      interface Observer {
          update(message: string): void;
      }
      
      class ConcreteObserver implements Observer {
          constructor(private name: string) {}
      
          update(message: string): void {
              console.log(`${this.name} received message: ${message}`);
          }
      }
      
      class Subject {
          private observers: Observer[] = [];
      
          public addObserver(observer: Observer): void {
              this.observers.push(observer);
          }
      
          public removeObserver(observer: Observer): void {
              const index = this.observers.indexOf(observer);
              if (index !== -1) {
                  this.observers.splice(index, 1);
              }
          }
      
          public notifyObservers(message: string): void {
              this.observers.forEach(observer => observer.update(message));
          }
      }
      
      const observer1 = new ConcreteObserver("Observer 1");
      const observer2 = new ConcreteObserver("Observer 2");
      
      const subject = new Subject();
      subject.addObserver(observer1);
      subject.addObserver(observer2);
      
      subject.notifyObservers("Hello, Observers!");
  • 设计模式在实际项目中的应用示例:

    • 应用场景:

      • 在一个电商系统中,购物车模块使用观察者模式,当用户在购物车中添加或删除商品时,通知其他相关模块(如价格显示模块、优惠券模块等)进行更新。
    • 实现示例:

      typescript 复制代码
      interface CartObserver {
          updateTotalPrice(totalPrice: number): void;
      }
      
      class ShoppingCart {
          private items: { name: string; price: number }[] = [];
          private observers: CartObserver[] = [];
      
          public addItem(item: { name: string; price: number }): void {
              this.items.push(item);
              this.notifyObservers();
          }
      
          public removeItem(item: { name: string; price: number }): void {
              const index = this.items.findIndex(i => i.name === item.name);
              if (index !== -1) {
                  this.items.splice(index, 1);
                  this.notifyObservers();
              }
          }
      
          public addObserver(observer: CartObserver): void {
              this.observers.push(observer);
          }
      
          public removeObserver(observer: CartObserver): void {
              const index = this.observers.indexOf(observer);
              if (index !== -1) {
                  this.observers.splice(index, 1);
              }
          }
      
          private calculateTotalPrice(): number {
              return this.items.reduce((total, item) => total + item.price, 0);
          }
      
          private notifyObservers(): void {
              const totalPrice = this.calculateTotalPrice();
              this.observers.forEach(observer => observer.updateTotalPrice(totalPrice));
          }
      }
      
      class PriceDisplay implements CartObserver {
          public updateTotalPrice(totalPrice: number): void {
              console.log(`Total Price Updated: $${totalPrice.toFixed(2)}`);
          }
      }
      
      const shoppingCart = new ShoppingCart();
      const priceDisplay = new PriceDisplay();
      
      shoppingCart.addObserver(priceDisplay);
      
      shoppingCart.addItem({ name: 'Product A', price: 20 });
      shoppingCart.addItem({ name: 'Product B', price: 30 });
      shoppingCart.removeItem({ name: 'Product A', price: 20 });

在实际项目中,设计模式可以帮助我们更好地组织和扩展代码,提高代码的可维护性和可读性。在购物车模块中使用观察者模式,可以很容易地实现对购物车状态的监听,并在状态发生变化时通知其他模块进行相应更新。

总结与展望

回顾整个学习过程,我们不仅从零开始学习了 TypeScript 的基础知识,还深入了解了一些高级特性。在学习的路上,我们克服了一些挑战,应用所学知识到了实际项目中。

对于 TypeScript 的未来趋势,我们展望着它不断演进的语言特性、蓬勃发展的生态系统,以及在前端技术栈中的日益重要的地位。我们鼓励大家保持持续学习的态度,不断实践和创新,参与技术社区,分享经验,共同推动前端技术的发展。

最后,希望这篇文章为前端开发新手提供了一条清晰的学习之路,激发了大家对 TypeScript 的兴趣,引导大家在前端领域迈出坚实的步伐。愿大家在不断学习的过程中,收获更多技能和成就。前路漫漫,让我们共同努力,迎接更多挑战和机遇!

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
qq_392794488 小时前
前端缓存策略:强缓存与协商缓存深度剖析
前端·缓存