编程进阶网 编程进阶网
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 在线工具
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 加解密
  • 时间日期
  • 网络工具
  • 颜色设计
  • 二维码
  • 开发实用
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++编程技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • README
  • Android提升进阶

  • iOS开发和进阶

  • Web开发和进阶

    • README
    • HTML工具手册

    • CSS样式与布局

    • JavaScript核心

    • TypeScript入门

      • README
      • TS类型系统基础
      • TS接口与对象类型
      • TS函数与类实战
        • 1. 案例引入
          • 1.1 一个"写了文档都没人看"的函数
          • 1.2 函数重载的解法
        • 2. 函数类型表达式
          • 2.1 三种定义函数类型的方式
          • 2.2 参数的可选与默认值
          • 2.3 参数解构的类型标注
        • 3. 函数重载
          • 3.1 重载的语法规则
          • 3.2 重载的黄金法则
          • 3.3 重载在实际场景中的最佳实践
        • 4. this 参数声明
          • 4.1 典型陷阱:回调中的 this
          • 4.2 TS 的 this 参数
          • 4.3 三种绑定方式对比
        • 5. 类:字段与构造器
          • 5.1 字段声明的三种位置
          • 5.2 参数属性(Parameter Properties)
          • 5.3 getters 与 setters
        • 6. 访问修饰符
          • 6.1 public / protected / private
          • 6.2 JS 原生 # 私有字段(ES2022)
        • 7. 继承与抽象类
          • 7.1 extends 继承
          • 7.2 abstract 抽象类
          • 7.3 implements:实现接口
        • 8. 实战案例:状态机模式
        • 9. 速查表
      • TS泛型编程实战
      • TS高级类型编程
      • TS类型守卫机制
      • TS模块系统详解
      • TS工程配置实践实践
    • Vue高级进阶

    • Web工程化实践

  • Linux应用开发

  • IoT智能硬件开发

  • Apps
  • Web开发和进阶
  • TypeScript入门
杨充
2025-06-24
目录

TS函数与类实战

# 03.TS函数与类实战

从函数重载到抽象类——TS 如何为 JS 的动态性穿上类型铠甲,让 OOP 回归安全。

# 1. 案例引入

# 1.1 一个"写了文档都没人看"的函数

// 运营同事要你做:传入数字返回 id 查询,传入对象返回批量查询
function query(params) {
    if (typeof params === "number") {
        return fetchByID(params);
    } else {
        return fetchByIDs(params.ids);
    }
}
// 调用者不知道到底返回什么类型
// 代码联想也无法给出提示

# 1.2 函数重载的解法

function query(id: number): User;
function query(params: { ids: number[] }): User[];
function query(params: number | { ids: number[] }): User | User[] {
    if (typeof params === "number") {
        return fetchByID(params);
    }
    return fetchByIDs(params.ids);
}

// 现在 IDE 自动识别:
const user = query(42);           // user: User
const users = query({ids:[1,2]}); // users: User[]

这就是本节要讲的——如何用 函数类型系统 + 类系统 写出类型安全的可维护代码。


# 2. 函数类型表达式

# 2.1 三种定义函数类型的方式

// 方式 1:类型别名
type Add = (a: number, b: number) => number;

// 方式 2:interface(调用签名)
interface Add2 {
    (a: number, b: number): number;
}

// 方式 3:直接标注
const add: (a: number, b: number) => number = (a, b) => a + b;

// 推荐:方式 1 或 3 用于一般场景;方式 2 用于需要声明合并的函数类型

# 2.2 参数的可选与默认值

// 可选参数 → 自动加 undefined 到类型
function greet(name: string, greeting?: string): string {
    return `${greeting || "Hello"}, ${name}`;
}

// 默认值参数 → 自动推断类型,且变成可选
function createUser(name: string, age = 18) {
    // age: number(自动推断),调用时可选
}

// 剩余参数
function sum(...nums: number[]): number {
    return nums.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4);  // ✅

# 2.3 参数解构的类型标注

// 对象解构标注——注意类型标注写在解构后面
function draw({ x, y, color }: { x: number; y: number; color?: string }): void {
    console.log(`Drawing at (${x}, ${y}) with ${color}`);
}

// 推荐:提前定义类型
interface Point {
    x: number;
    y: number;
}
function move(point: Point, dx: number, dy: number): Point {
    return { x: point.x + dx, y: point.y + dy };
}

# 3. 函数重载

# 3.1 重载的语法规则

// 重载签名(仅声明,不实现)——可以有多个
function format(value: string): string;
function format(value: number): string;
function format(value: boolean): string;

// 实现签名(兼容所有重载签名)
function format(value: string | number | boolean): string {
    if (typeof value === "string") return `"${value}"`;
    if (typeof value === "number") return value.toFixed(2);
    return value ? "true" : "false";
}

format("hello");  // string → IDE 显示对应重载签名
format(3.14159);   // string
format(true);      // string

# 3.2 重载的黄金法则

// 法则 1:实现签名必须兼容所有重载签名
function getData(id: number): User;
function getData(ids: number[]): User[];
// ✅ 好——实现签名用更泛化的类型
function getData(idOrIds: number | number[]): User | User[] { /* ... */ }

// ❌ 坏——实现签名太窄
// function getData(id: number): User | User[] { /* ... */ }

// 法则 2:优先使用联合类型而非重载(除非返回值形状天差地别)
// 能写 len(s: string | any[]): number → 就不要重载

# 3.3 重载在实际场景中的最佳实践

// 真实案例:表单提交——返回类型随参数变化
function submit<T>(data: T): Promise<T>;
function submit<T>(data: T, options: { retry: number }): Promise<{ result: T; retries: number }>;
function submit<T>(
    data: T,
    options?: { retry: number }
): Promise<T | { result: T; retries: number }> {
    if (options) {
        let retries = 0;
        while (retries <= options.retry) {
            try { return Promise.resolve({ result: data, retries }); }
            catch { retries++; }
        }
        throw new Error("Failed after retries");
    }
    return Promise.resolve(data);
}

const a = submit({ name: "John" });           // Promise<{name:string}>
const b = submit({ name: "John" }, { retry: 3 }); // Promise<{result:{name:string},retries:number}>

# 4. this 参数声明

# 4.1 典型陷阱:回调中的 this

class Counter {
    count = 0;
    increment() {
        this.count++;
    }
}

const c = new Counter();
const fn = c.increment;
// fn();  // ❌ 运行时:this 为 undefined(严格模式)

# 4.2 TS 的 this 参数

class Counter {
    count = 0;

    // this 作为"假参数"——编译后消失,仅用于类型检查
    increment(this: Counter) {
        this.count++;
    }
}

// 现在传递时的类型错误就会被捕获
// const fn: (this: Counter) => void = c.increment;
// fn();  // ❌ 类型错误:缺少 this 上下文

// 也可以在非类函数中指定 this
interface UIContext {
    element: HTMLElement;
    onClick: (this: UIContext, e: MouseEvent) => void;
}

# 4.3 三种绑定方式对比

class Button {
    label = "Click";

    // 方式 1:箭头函数属性(自动绑定 this)
    onClick = () => {
        console.log(this.label);  // ✅ this 永远是 Button 实例
    };

    // 方式 2:显式 this 参数
    onClick2(this: Button) {
        console.log(this.label);
    }

    // 方式 3:bind 绑定(运行时,TS 检查不了)
}

// 选型:箭头函数属性最安全(但占用实例内存,非共享原型方法)

# 5. 类:字段与构造器

# 5.1 字段声明的三种位置

class User {
    // 字段声明
    name: string;
    age: number;
    // 带初始值(自动推断类型)
    createdAt = new Date();
    // 非空断言(绕过 definite assignment check)
    id!: number;

    constructor(name: string, age: number, id: number) {
        this.name = name;
        this.age = age;
        this.id = id;
    }
}

# 5.2 参数属性(Parameter Properties)

// 传统写法——重复声明
class OldStyle {
    name: string;
    age: number;
    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }
}

// 参数属性——简洁 50%
class NewStyle {
    constructor(
        public name: string,   // 自动 this.name = name
        public age: number     // 自动 this.age = age
    ) {}
}

# 5.3 getters 与 setters

class Circle {
    constructor(private _radius: number) {}

    get radius(): number {
        return this._radius;
    }

    set radius(value: number) {
        if (value <= 0) throw new Error("半径必须 > 0");
        this._radius = value;
    }

    get area(): number {
        return Math.PI * this._radius ** 2;
    }
}

const c = new Circle(5);
console.log(c.area);    // 78.54... getter 像属性一样访问
c.radius = 10;          // setter 触发校验

# 6. 访问修饰符

# 6.1 public / protected / private

class Animal {
    public name: string;      // 默认 public——任何地方可访问
    protected age: number;    // 自身 + 子类可访问
    private _secret: string;  // 仅自身可访问

    constructor(name: string, age: number, secret: string) {
        this.name = name;
        this.age = age;
        this._secret = secret;
    }
}

class Dog extends Animal {
    bark() {
        console.log(this.name);     // ✅ public
        console.log(this.age);      // ✅ protected——子类可访问
        // console.log(this._secret); // ❌ private 不可
    }
}

const dog = new Dog("旺财", 3, "xxx");
dog.name;          // ✅
// dog.age;        // ❌ protected 外部不可
// dog._secret;    // ❌ private

注意:TS 的 private 只是编译时约束,运行时仍然可以通过 dog["_secret"] 访问。这是 TS 的类型擦除特性——让 OOP 在编码阶段安全即可。

# 6.2 JS 原生 # 私有字段(ES2022)

class SecureClass {
    #password: string;  // JS 原生私有字段——运行时也不可访问
    private tsSecret: string;  // TS 编译时私有

    constructor(password: string, secret: string) {
        this.#password = password;
        this.tsSecret = secret;
    }
}

// 选型:
// 需要真正运行时私密 → # 字段
// 仅需编译时 UML 式封装 → private

# 7. 继承与抽象类

# 7.1 extends 继承

class Vehicle {
    constructor(protected speed: number) {}

    move(): string {
        return `以 ${this.speed} km/h 移动`;
    }
}

class Car extends Vehicle {
    constructor(speed: number, private brand: string) {
        super(speed);  // 子类 constructor 必须先调用 super()
    }

    override move(): string {       // 显式声明 override(推荐)
        return `${this.brand} ${super.move()}`;
    }
}

# 7.2 abstract 抽象类

abstract class Shape {
    abstract getArea(): number;   // 抽象方法——子类必须实现
    abstract perimeter: number;   // 抽象只读属性

    // 也可以有具体实现
    describe(): string {
        return `面积: ${this.getArea()}, 周长: ${this.perimeter}`;
    }
}

class Rectangle extends Shape {
    constructor(private w: number, private h: number) {
        super();
    }
    getArea(): number { return this.w * this.h; }
    get perimeter(): number { return 2 * (this.w + this.h); }
}

// const s = new Shape();  // ❌ 不能实例化抽象类
const r = new Rectangle(3, 4);
r.describe();  // "面积: 12, 周长: 14"

# 7.3 implements:实现接口

interface Flyable {
    fly(): void;
    altitude: number;
}

interface Swimmable {
    swim(): void;
}

class Duck implements Flyable, Swimmable {
    altitude = 0;

    fly() { this.altitude += 10; }
    swim() { console.log("swimming"); }
}
// TS 检查:Duck 必须同时实现 fly() 和 swim() + 包含 altitude 属性

# 8. 实战案例:状态机模式

// 用 TS 类和接口设计一个订单状态机

// 状态定义
type OrderState = "pending" | "paid" | "shipped" | "delivered" | "cancelled";

// 状态转移表(只允许合法转移)
const transitions: Record<OrderState, OrderState[]> = {
    pending: ["paid", "cancelled"],
    paid: ["shipped", "cancelled"],
    shipped: ["delivered"],
    delivered: [],
    cancelled: []
};

abstract class OrderBase {
    constructor(
        protected id: string,
        public state: OrderState,
        protected createdAt: Date
    ) {}

    protected canTransitionTo(target: OrderState): boolean {
        return transitions[this.state].includes(target);
    }

    abstract transition(target: OrderState): void;
    abstract getInfo(): string;
}

class Order extends OrderBase {
    private updatedAt: Date;

    constructor(id: string) {
        super(id, "pending", new Date());
        this.updatedAt = new Date();
    }

    override transition(target: OrderState): void {
        if (!this.canTransitionTo(target)) {
            throw new Error(`不允许从 ${this.state} 转到 ${target}`);
        }
        this.state = target;
        this.updatedAt = new Date();
    }

    override getInfo(): string {
        return `订单 ${this.id}: ${this.state} | 创建: ${this.createdAt.toISOString()}`;
    }
}

const order = new Order("ORD-001");
order.transition("paid");     // ✅
// order.transition("shipped"); // 抛出异常(该状态不允许转移)

# 9. 速查表

特性 语法 关键说明
函数类型 (a:number) => string 箭头语法,返回类型在 => 后
调用签名 { (a:number): string } interface 形式
重载 多个签名 + 一个实现 实现签名必须兼容所有重载
可选参数 x?: string 自动加 \| undefined
this 参数 (this: MyClass, ...) 编译后移除,仅用于检查
参数属性 constructor(public n: number) 一行三用(字段+构造+赋值)
get/set get x(): number 像属性一样访问
private private x 编译时私有,运行时可绕过
protected protected x 自身 + 子类可访问
abstract abstract class/method 不可实例化,子类必须实现
override override method() 显式标记覆盖,防重构错误
implements class A implements I 实现多个接口

下一篇:04.TS泛型编程实战

上次更新: 2026/06/24, 12:59:24
TS接口与对象类型
TS泛型编程实战

← TS接口与对象类型 TS泛型编程实战→

最近更新
01
CSS选择器入门
06-23
02
CSS定位与层级
06-23
03
CSS盒模型详解
06-23
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 鄂ICP备2024073355号-1 | 鄂ICP备2024073355号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式