目录
[一、TS 核心基础语法](#一、TS 核心基础语法)
[四、观察者模式(发布 - 订阅模式)](#四、观察者模式(发布 - 订阅模式))
[TypeScript 基础语法速查表](#TypeScript 基础语法速查表)
一、TS 核心基础语法
先搞懂这些基础语法,再看后续代码会事半功倍,这是 TS 面向对象编程的核心基石。
表格
| 语法关键字 | 核心作用 | 通俗理解 |
|---|---|---|
interface |
定义类型契约 / 规范,约束类、对象、函数的结构 | 相当于一份 "合同",签了合同的类,必须实现合同里约定的所有内容 |
class |
定义类,TS 面向对象的核心,用来封装属性和方法 | 相当于一个 "模板",可以用new关键字根据模板创建实例对象 |
implements |
类实现接口,强制类必须符合接口的结构规范 | 类 "签订" 了 interface 合同,必须兑现合同里的所有承诺 |
extends |
类的继承,子类可复用父类的属性和方法 | 子类继承父类的能力,同时可以扩展自己独有的功能 |
public/private |
访问修饰符,控制属性 / 方法的访问权限 | public:公开的,任何地方都能访问(TS 默认值);private:私有的,只有类内部能访问 |
static |
静态修饰符,修饰的属性 / 方法属于类本身,而非实例 | 无需new创建实例,直接用类名.xxx即可访问 |
enum |
枚举,给一组固定取值起友好名称,限定取值范围 | 把固定选项(如车型、状态)打包管理,避免硬编码的魔法字符串 / 数字 |
get/set |
访问器,拦截属性的读取 / 赋值操作,可嵌入额外逻辑 | 给属性加 "门禁",读写属性时会先经过门禁,可做校验、触发事件等操作 |
: 类型 |
类型注解,给变量 / 参数 / 返回值指定类型,是 TS 类型校验的核心 | 给数据贴 "类型标签",TS 会检查使用是否合规,提前发现错误 |
any |
任意类型,关闭 TS 的类型校验 | 相当于给数据 "开绿灯",TS 不再做类型检查,新手慎用,会丢失 TS 的类型优势 |
二、代理模式(委托模式)
模式简介
代理模式(也叫委托模式),核心思想是:调用者不直接执行具体操作,而是把操作委托给代理对象执行。可以动态切换不同的代理实现,将 "调用操作" 和 "具体实现" 解耦,大幅提升代码扩展性。
// 1. 定义接口:约定计算方法的规范(合同)
// 接口名前加I是TS通用规范,代表Interface,方便识别
interface Icalc {
// 约定:必须有calc方法,接收2个参数,返回值必须是数字类型
calc(num1: any, num2: any): number;
}
// 2. 实现类1:加法计算,严格遵守Icalc接口的约定
// implements关键字:声明这个类要实现Icalc接口,必须实现接口里的calc方法
class Npc1 implements Icalc {
// 实现接口约定的calc方法,参数、返回值与接口保持一致
calc(num1: any, num2: any) {
// 实现加法逻辑,返回两数之和
return num1 + num2;
}
}
// 3. 实现类2:减法计算,同样遵守Icalc接口的约定
class Npc2 implements Icalc {
// 实现接口约定的calc方法,这里编写减法逻辑
calc(num1: any, num2: any) {
return num1 - num2;
}
}
// 4. 调用者类:Person,不自己实现计算逻辑,全部委托给代理对象
class Person {
// 定义代理属性,类型为Icalc接口
// 含义:这个属性只能接收"实现了Icalc接口的类的实例"
// !是TS非空断言,告诉TS该属性一定会在使用前赋值,避免初始化报错
delegate!: Icalc;
// 对外暴露的获取计算结果的方法
Getnum(num1: any, num2: any) {
// 自身不做计算,委托给代理对象的calc方法执行
let num = this.delegate.calc(num1, num2);
// 将结果输出到页面
document.write(num + "");
}
}
// 5. 实际使用
// 创建Person实例
let person = new Person();
// 给person设置代理:使用加法实现类Npc1的实例
person.delegate = new Npc1();
// 调用方法,实际执行Npc1的加法逻辑,输出91
person.Getnum(13, 78);
// 【扩展性演示】想换成减法?一行代码切换代理,无需修改Person类的任何代码
// person.delegate = new Npc2();
// person.Getnum(13, 78); // 输出-65
必知知识点补充
- 接口的核心价值 接口只定义 "规范",不编写具体实现。所有实现了该接口的类,都有统一的方法名和参数结构,调用时无需关心具体是哪个类,只需知道它具备对应的方法,这就是面向接口编程。
- 代理模式的优势 比如示例中想要新增乘法、除法逻辑,只需新建一个实现
Icalc接口的类,无需修改Person类的代码,符合开闭原则(对扩展开放,对修改关闭)。 - 新手易错点提醒
- 原代码中
delegate: Icalc;没有初始化,TS 会报 "属性未初始化" 的错误,解决方案:- 加非空断言
!,告诉 TS 该属性一定会在使用前赋值; - 在构造函数中给默认值;
- 加非空断言
- 尽量少用
any类型,示例中可改为number类型,更符合 TS 的类型规范,比如calc(num1: number, num2: number): number。
三、简单工厂模式
模式简介
简单工厂模式(也叫静态工厂模式),核心思想是:封装对象的创建逻辑,对外提供统一的工厂方法。调用者无需关心对象的创建细节,只需传入对应的类型,工厂就会返回匹配的实例对象,将 "对象创建" 和 "对象使用" 解耦。
// 1. 定义枚举:限定车型的可选值,避免硬编码魔法字符串
// 枚举会给每个选项默认分配数字,Bmw=0, Audi=1, Benz=2,也可手动赋值
enum CarType {
Bmw,
Audi,
Benz
}
// 2. 父类:汽车基类,所有车型都继承该类
class Car {
// 公共属性:车名
name: string;
// 3. 静态工厂方法:核心,用来创建汽车实例
// static修饰:该方法属于Car类本身,直接用Car.Create()调用,无需new Car()
// 参数:车型枚举,返回值:Car类的实例
static Create(carType: CarType): Car {
// 定义变量,存储创建好的汽车实例
let car: Car;
// 根据传入的车型,创建对应的实例
switch (carType) {
case CarType.Audi:
car = new Audi();
break;
case CarType.Benz:
car = new Benz();
break;
case CarType.Bmw:
car = new Bmw();
break;
default:
throw new Error("不支持的车型");
}
// 返回创建好的实例
return car;
}
}
// 4. 子类:宝马,继承Car父类
// extends关键字:继承父类的所有属性和方法
class Bmw extends Car {
constructor() {
super(); // 必须调用super(),执行父类的构造函数
this.name = "宝马";
}
}
// 子类:奔驰,继承Car父类
class Benz extends Car {
constructor() {
super();
this.name = "奔驰";
}
}
// 子类:奥迪,继承Car父类
class Audi extends Car {
constructor() {
super();
this.name = "奥迪";
}
}
// 5. 实际使用
// 调用工厂方法,传入车型,直接拿到对应实例,无需自己new Benz()
let bmw = Car.Create(CarType.Benz);
console.log(bmw.name); // 输出:奔驰
// 想创建奥迪,只需修改传入的枚举值,无需关心Audi类的创建细节
let audi = Car.Create(CarType.Audi);
console.log(audi.name); // 输出:奥迪
必知知识点补充
- 枚举的优势 用
CarType.Bmw代替硬编码的字符串 / 数字,代码可读性更高,且 TS 会做类型校验,只能传入枚举中定义的值,避免传参错误。 - static 静态成员的特点 静态属性 / 方法属于类本身 ,而非实例。因此无需
new Car()创建实例,直接用Car.Create()即可调用;且静态方法中不能用this访问实例的属性 / 方法,只能访问静态成员。 - 继承中的 super () 子类的构造函数中,必须先调用
super(),它会执行父类的构造函数,只有调用super()之后,才能在子类构造函数中使用this。 - 简单工厂的适用场景当需要创建的对象类型不多、创建逻辑不复杂时使用,比如 UI 组件库的弹窗、按钮创建,统一封装创建逻辑。
四、观察者模式(发布 - 订阅模式)
模式简介
观察者模式(也叫发布 - 订阅模式),核心思想是:定义一对多的依赖关系,当一个对象(被观察者 / 主题)的状态发生变化时,会自动通知所有依赖它的观察者对象,观察者收到通知后执行对应逻辑。这是前端最常用的模式之一,Vue 的响应式、事件监听、状态管理都基于这个核心思想。
// 1. 定义观察者接口:约定观察者必须实现的方法
interface IObserver {
// 约定:被观察者状态变化时,会调用该方法,传入新的状态值
nameChanged(newName: string): void;
}
// 2. 被观察者类:Person,也叫主题/发布者
class Person {
// 私有属性:真正存储名字的变量,加_是TS通用规范,代表私有属性
// private修饰符:只有Person类内部能访问,外部无法直接修改
private _name: string;
// 观察者数组:存放所有注册的观察者实例
// Array<IObserver>:泛型数组,只能存放实现了IObserver接口的实例
observers: Array<IObserver> = new Array<IObserver>();
// set访问器:拦截对name属性的赋值操作
// 当给person.name赋值时,会自动执行这个set方法
set name(value: string) {
// 给私有属性赋值,更新状态
this._name = value;
// 【核心】状态变化后,遍历所有观察者,给每个观察者发送通知
for (let observer of this.observers) {
// 调用观察者的nameChanged方法,传递新值
observer.nameChanged(this._name);
}
}
// get访问器:拦截对name属性的读取操作
// 当读取person.name时,会自动执行这个get方法
get name() {
// 返回私有属性的值
return this._name;
}
}
// 3. 观察者类:Test,实现了IObserver接口
class Test implements IObserver {
// 实现接口约定的方法,收到通知后执行的业务逻辑
nameChanged(newName: string) {
// 这里可编写任意业务逻辑,比如更新UI、触发接口请求等
document.write("监听到变化,名字变为" + newName);
}
}
// 4. 实际使用
// 创建被观察者实例
let person = new Person();
// 创建观察者实例
let test = new Test();
// 将观察者注册到被观察者的数组中,相当于"订阅"状态变化
person.observers.push(test);
// 给name赋值,触发set访问器,自动通知所有观察者
// 执行这行代码后,页面会输出"监听到变化,名字变为哈哈哈"
person.name = "哈哈哈";
// 【扩展性演示】可添加无数个观察者,都会收到通知
// class Test2 implements IObserver {
// nameChanged(newName: string) {
// console.log("Test2也监听到了:", newName);
// }
// }
// person.observers.push(new Test2());
// person.name = "新名字"; // 两个观察者都会收到通知
必知知识点补充
- get/set 访问器的作用 它可以拦截属性的读写操作,我们可以在其中嵌入额外逻辑,比如赋值校验、触发事件、日志打印等,Vue2 的响应式原理就是基于
Object.defineProperty的 get/set 实现的。 - 观察者模式的核心优势被观察者和观察者完全解耦:被观察者只负责 "发布通知",无需关心观察者收到通知后的行为;观察者只负责 "处理通知",无需关心状态何时变化。可以随时新增 / 删除观察者,无需修改原有代码。
- for...of 遍历用来遍历数组的每一项,比传统 for 循环更简洁,新手可直接用它遍历数组。
- 泛型数组
Array<T>Array<IObserver>的含义是 "这个数组里只能存放实现了 IObserver 接口的实例",TS 会做类型校验,放入其他类型会直接报错,这就是泛型的类型约束作用。
五、单例模式
模式简介
单例模式,核心思想是:保证一个类在全局范围内只有一个实例,并且提供一个全局统一的访问点。比如全局状态管理、音频管理器、弹窗管理器,都适合用单例实现,既能避免重复创建实例浪费资源,也能保证全局状态的一致性。
// 全局音频管理器,单例模式实现
class SoundManager {
// 1. 私有静态属性:存放全局唯一的实例
// private:外部无法直接访问
// static:属于类本身,而非实例
private static instance: SoundManager;
// 2. 私有构造函数!单例实现的核心
// 构造函数加了private,外部就无法用new SoundManager()创建实例
// 从根源上限制了实例的创建,保证全局只有一个实例
private constructor() {}
// 3. 静态方法:全局唯一的访问点,用来获取实例
static Instance() {
// 【懒加载核心】判断实例是否已经创建
if (!SoundManager.instance) {
// 只有第一次调用时,才会创建实例
SoundManager.instance = new SoundManager();
}
// 永远返回同一个实例
return SoundManager.instance;
}
// 给类添加业务方法,比如播放音频
playSound() {
console.log("播放音频");
}
}
// 4. 实际使用
// 永远只能通过SoundManager.Instance()获取实例,禁止new
const manager1 = SoundManager.Instance();
const manager2 = SoundManager.Instance();
// 验证:两个变量指向同一个实例,输出true
console.log(manager1 === manager2);
// 调用业务方法
manager1.playSound(); // 输出:播放音频
// 【错误演示】构造函数是private,外部new会直接报错
// const manager3 = new SoundManager(); // 报错:构造函数是私有的,仅可在类声明中访问
新手必知知识点补充
-
私有构造函数的作用 普通类的构造函数是 public 的,外部可以随时
new创建实例;构造函数加了private之后,只有类内部能调用new,外部完全无法创建实例,从根源上限制了实例的数量。 -
懒加载(懒汉式)的优势 上面的写法是懒汉式单例,只有第一次调用
Instance()方法时,才会创建实例。如果这个类从未被使用,就不会创建实例,节省内存资源。 -
**饿汉式单例(另一种常用写法)**还有一种饿汉式写法,类加载时就直接创建实例,代码更简洁:
class SoundManager {
// 类加载时就直接创建实例,赋值给静态属性
private static instance: SoundManager = new SoundManager();
private constructor() {}
// 直接返回已经创建好的实例
static Instance() {
return SoundManager.instance;
}
}
两者区别:饿汉式是提前创建,懒汉式是使用时再创建,新手可根据场景选择。4. 单例的适用场景全局只需要一个的对象,比如全局配置、日志管理器、资源加载器、全局弹窗等,保证全局状态统一,避免资源浪费。
TypeScript 基础语法速查表
这张表将 TS 新手必备的所有核心语法整合在一起,涵盖变量声明、数据类型、函数、面向对象、模块等全方面内容,方便随时查阅。
| 分类 | 语法 / 关键字 | 核心作用 | 新手通俗理解 | 代码示例 | ||
|---|---|---|---|---|---|---|
| 变量声明 | let |
声明块级作用域变量,可重新赋值 | 最常用的变量声明方式,替代旧的 var,只在当前 {} 内有效 |
let age = 25;``age = 26; ✅ 可重新赋值 |
||
const |
声明块级作用域常量,声明后不可重新赋值 | 用来声明不会变化的固定值,声明时必须赋值,保证数据不可变 | const PI = 3.14;``const user = { name: "张三" };``user.name = "李四"; ✅ 对象内容可改 |
|||
var |
声明函数级作用域变量,有变量提升 | ❌ 新手别用!历史遗留语法,作用域混乱 | var a = 10; // 不推荐 |
|||
| 基础数据类型 | string |
字符串类型 | 文本内容,用单引号 / 双引号 / 反引号包裹 | let name: string = "张三";``let greet: string = \Hello, ${name}`;` |
||
number |
数字类型 | 整数、小数、正数、负数都可以 | let age: number = 25;``let price: number = 99.99; |
|||
boolean |
布尔类型 | 只有两个值:true(真)和 false(假) |
let isLogin: boolean = true; |
|||
null |
空值类型 | 表示 "什么都没有",是一个空对象引用 | let data: null = null; |
|||
undefined |
未定义类型 | 变量声明了但没赋值,默认就是 undefined |
let temp: undefined = undefined; |
|||
void |
空返回值类型 | 专门用于函数,表示函数没有返回值 | function log(msg: string): void { console.log(msg); } |
|||
never |
永不存在值类型 | 表示函数永远不会正常结束(抛错 / 死循环) | function throwError(msg: string): never { throw new Error(msg); } |
|||
unknown |
未知类型 | 安全版的 any,使用前必须先做类型检查 |
let value: unknown = "hello";``if (typeof value === "string") { value.toUpperCase(); } |
|||
any |
任意类型 | 关闭 TS 类型校验,相当于 "开绿灯",新手慎用! | let anything: any = 10;``anything = "hello"; // 不报错但不安全 |
|||
| 复杂数据类型 | 类型[] / Array<类型> |
数组类型 | 存放一组相同类型的数据 | let nums: number[] = [1, 2, 3];``let names: Array<string> = ["张三", "李四"]; |
||
{ 属性名: 类型; ... } |
对象类型 | 描述对象的结构,约束对象的属性和类型 | let user: { name: string; age: number } = { name: "张三", age: 25 }; |
|||
type 别名 = 类型 |
类型别名 | 给复杂类型起个简短的名字,方便复用 | type User = { name: string; age: number };``let user: User = { name: "张三", age: 25 }; |
|||
| `类型1 | 类型2` | 联合类型 | 表示值可以是多种类型中的一种 | **`let value: string | number = "hello";``value = 100;` ✅ 两种都可以** | |
类型1 & 类型2 |
交叉类型 | 表示值必须同时满足多种类型 | type A = { name: string };``type B = { age: number };``let user: A & B = { name: "张三", age: 25 }; |
|||
(参数: 类型) => 返回值 |
函数类型 | 描述函数的参数类型和返回值类型 | type AddFn = (a: number, b: number) => number;``const add: AddFn = (a, b) => a + b; |
|||
值 as 类型 |
类型断言 | 告诉 TS"我比你更清楚这个值的类型",强制指定 | let value: unknown = "hello";``let len: number = (value as string).length; |
|||
| 函数语法 | function 函数名(参数: 类型): 返回值 { } |
函数声明 | 定义一个有名字的函数 | function add(a: number, b: number): number { return a + b; } |
||
参数?: 类型 |
可选参数 | 表示这个参数可以传也可以不传,不传为 undefined |
function greet(name?: string): string { return \Hello, ${name |
|||
参数: 类型 = 默认值 |
默认参数 | 给参数设置默认值,不传参时自动使用 | function greet(name: string = "陌生人"): string { return \Hello, ${name}`; }` |
|||
...参数名: 类型[] |
剩余参数 | 把多个剩余参数收集成一个数组 | function sum(...nums: number[]): number { return nums.reduce((a, b) => a + b, 0); } |
|||
| 面向对象 | interface 接口名 { } |
定义接口 | 定义类型契约 / 规范,约束类、对象的结构 | interface User { name: string; age: number; } |
||
class 类名 { } |
定义类 | 面向对象的核心,封装属性和方法的模板 | class Person { name: string; sayHi() { console.log("Hi"); } } |
|||
class 类名 implements 接口名 { } |
类实现接口 | 强制类必须符合接口的结构规范 | interface IUser { name: string; }``class User implements IUser { name: string; } |
|||
class 子类 extends 父类 { } |
类的继承 | 子类继承父类的所有属性和方法,同时可扩展 | class Animal { name: string; }``class Dog extends Animal { bark() { console.log("汪汪"); } } |
|||
public |
公共访问修饰符 | 公开的,任何地方都能访问(TS 默认值,可省略) | class Person { public name: string; } |
|||
private |
私有访问修饰符 | 私有的,只有类内部能访问,外部和子类都不行 | class Person { private _age: number; } |
|||
protected |
受保护访问修饰符 | 受保护的,只有类内部和子类能访问,外部不行 | class Person { protected gender: string; } |
|||
readonly |
只读修饰符 | 修饰的属性只能读取,不能修改 | class Person { readonly id: number; constructor(id: number) { this.id = id; } } |
|||
static |
静态修饰符 | 修饰的属性 / 方法属于类本身,直接用类名调用 | class Utils { static PI = 3.14; }``Utils.PI; // 直接调用 |
|||
get / set |
访问器 | 拦截属性的读取 / 赋值操作,可嵌入额外逻辑 | class Person { private _name: string; get name() { return this._name; } set name(value) { this._name = value; } } |
|||
abstract class |
抽象类 | 不能直接 new 的类,专门用来被继承 |
abstract class Animal { abstract makeSound(): void; } |
|||
abstract 方法名() |
抽象方法 | 只有方法签名,没有具体实现,子类必须实现 | abstract class Animal { abstract makeSound(): void; }``class Dog extends Animal { makeSound() { console.log("汪汪"); } } |
|||
| 其他常用 | enum 枚举名 { 选项1, 选项2 } |
枚举 | 给一组固定取值起友好名称,限定取值范围 | enum Color { Red, Green, Blue }``let c: Color = Color.Red; |
||
typeof 变量 |
类型判断 | 获取变量的类型(运行时有效),常用于类型守卫 | let value: unknown = "hello";``if (typeof value === "string") { value.toUpperCase(); } |
|||
变量 instanceof 类 |
实例判断 | 判断变量是否是某个类的实例(运行时有效) | class Dog { }``let d = new Dog();``if (d instanceof Dog) { d.bark(); } |
|||
keyof 类型 |
键名获取 | 获取类型的所有键名,返回一个联合类型 | type User = { name: string; age: number };``type UserKeys = keyof User; // "name" | "age" |
|||
<T> |
泛型 | 让类型也可以像参数一样传递,实现类型复用 | function identity<T>(value: T): T { return value; }``identity<string>("hello"); |
|||
import / export |
模块导入导出 | 用于模块化开发,在不同文件间共享代码 | // a.ts``export const PI = 3.14;``// b.ts``import { PI } from "./a.ts"; |