- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
本文以开源项目 The Concise TypeScript Book 的韩语章节 website/src/content/docs/ko-kr/book/class.md(对应英文原版 class.md)为核心骨架,系统讲解 TypeScript 中类的定义、构造、访问控制、继承、泛型、装饰器与重载等全部核心能力。读完本文,你将掌握在 TypeScript 中编写类型安全、可复用、可扩展类结构的完整技能,并了解该项目如何通过自动化工具校验文档中每一段 TypeScript 代码的真实可编译性。
一、类(Class)的基础语法
在 TypeScript 中,class关键字用于定义一个类。下面是一个最基础的示例:
class Person { private name: string; private age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } public sayHi(): void { console.log( `Hello, my name is ${this.name} and I am ${this.age} years old.` ); } }对这个示例逐行拆解:
class关键字定义了一个名为Person的类;- 类中包含两个私有属性:
string类型的name和number类型的age; - 构造函数使用
constructor关键字定义,接收name与age参数并赋值给对应属性; - 类中有一个
public方法sayHi,用于打印问候信息。
要在 TypeScript 中创建类的实例,使用new关键字、后跟类名与括号()即可:
const myObject = new Person('John Doe', 25); myObject.sayHi(); // Output: Hello, my name is John Doe and I am 25 years old.值得一提的是,本项目对文档中的每一段 TypeScript 代码都会做真实编译校验。工具脚本 compile.ts 使用marked词法分析器提取 markdown 中的typescript代码块,写入临时文件后通过ts.createProgram以strict: true、noImplicitAny: true、noEmitOnError: true编译并报告诊断信息;带有<!-- skip -->注释的代码块(如上面的实例化示例,以及后文自动访问器的降级示例)会被跳过编译。也就是说,本文出现的绝大多数代码片段都是经该项目 CI 实际编译通过的真实代码。
二、构造函数(Constructor)与构造函数重载
构造函数是类中的特殊方法,当类实例被创建时用于初始化对象属性:
class Person { public name: string; public age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } sayHello() { console.log( `Hello, my name is ${this.name} and I'm ${this.age} years old.` ); } } const john = new Person('Simon', 17); john.sayHello();2.1 构造函数重载(Constructor Overloading)
使用如下语法可以对构造函数进行重载:
type Sex = 'm' | 'f'; class Person { name: string; age: number; sex: Sex; constructor(name: string, age: number, sex?: Sex); constructor(name: string, age: number, sex: Sex) { this.name = name; this.age = age; this.sex = sex ?? 'm'; } } const p1 = new Person('Simon', 17); const p2 = new Person('Alice', 22, 'f');TypeScript 允许定义多个构造函数重载签名,但只能有一个与所有重载都兼容的实现,这可以通过可选参数来实现:
class Person { name: string; age: number; constructor(); constructor(name: string); constructor(name: string, age: number); constructor(name?: string, age?: number) { this.name = name ?? 'Unknown'; this.age = age ?? 0; } displayInfo() { console.log(`Name: ${this.name}, Age: ${this.age}`); } } const person1 = new Person(); person1.displayInfo(); // Name: Unknown, Age: 0 const person2 = new Person('John'); person2.displayInfo(); // Name: John, Age: 0 const person3 = new Person('Jane', 25); person3.displayInfo(); // Name: Jane, Age: 25注意:多个重载签名在上、单个实现签名在下;实现签名的参数必须是所有重载参数的「并集」,通常通过可选参数(name?: string)与空值合并运算符(??)提供默认值来满足不同调用形态。
2.2 私有与受保护构造函数(Private and Protected Constructors)
TypeScript 可以将构造函数标记为private或protected,从而限制其可访问性与使用方式。
私有构造函数:只能在类自身内部调用。常用于强制实现单例(Singleton)模式,或把实例创建限制在类内部的工厂方法中。
受保护构造函数:适用于创建不应被直接实例化、但可被子类扩展的基类。
class BaseClass { protected constructor() {} } class DerivedClass extends BaseClass { private value: number; constructor(value: number) { super(); this.value = value; } } // Attempting to instantiate the base class directly will result in an error // const baseObj = new BaseClass(); // Error: Constructor of class 'BaseClass' is protected. // Create an instance of the derived class const derivedObj = new DerivedClass(10);这里的关键点在于:DerivedClass的构造函数中必须先调用super(),因为基类构造函数虽然是protected,但派生类仍然可以合法调用它来完成继承初始化。
三、访问修饰符(Access Modifiers)
访问修饰符private、protected、public用于控制类成员(属性与方法)的可见性和可访问性。它们是强制封装(encapsulation)、为访问和修改类内部状态划定边界的关键工具:
private:将成员访问限制在包含它的类内部;protected:允许在包含它的类及其派生类内部访问;public:提供无限制访问,可在任何位置访问(默认访问级别)。
class Person { private name: string; protected age: number; public email: string; constructor(name: string, age: number, email: string) { this.name = name; this.age = age; this.email = email; } }从本项目工具的编译配置看(tsconfig.json 启用了"strict": true),文档中的所有访问修饰符示例都在严格模式下编译,这意味着诸如「在类外访问private成员」这类错误会被编译器直接拦截,保证了文档示例的规范性。
四、getter 与 setter(Get and Set)
getter 和 setter 是特殊方法,允许为类属性定义自定义的读取与修改行为。它们能把对象的内部状态封装起来,并在取值或赋值时附加额外逻辑。TypeScript 中分别用get与set关键字定义:
class MyClass { private _myProperty: string; constructor(value: string) { this._myProperty = value; } get myProperty(): string { return this._myProperty; } set myProperty(value: string) { this._myProperty = value; } }实践中通常约定以_前缀命名私有字段(如_myProperty),再通过同名公开访问器暴露受控的读写接口,便于在 setter 中执行校验、转换或通知等逻辑。
五、类的自动访问器(Auto-Accessors)
TypeScript 4.9 起支持自动访问器(auto-accessors),这是一个即将纳入 ECMAScript 标准的新特性。它们看起来与类属性相似,但使用accessor关键字声明:
class Animal { accessor name: string; constructor(name: string) { this.name = name; } }自动访问器会被「降级(de-sugar)」为作用于一个不可访问属性的私有get/set访问器:
class Animal { #__name: string; get name() { return this.#__name; } set name(value: string) { this.#__name = value; } constructor(name: string) { this.name = name; } }可以看到,accessor关键字本质上是一个语法糖,编译期会自动生成基于私有字段#__name的 getter/setter 对,既保留了字段的私有性,又提供了与普通属性一致的使用体验。项目中该代码块带有<!-- skip -->标记,因为降级后的#私有字段写法在部分目标环境下不适用,故跳过编译校验(机制见 compile.ts 中isSkipComment的实现)。
六、this关键字
在 TypeScript 中,this关键字在方法或构造函数内指向类的当前实例,允许在类自身作用域内访问和修改类的属性与方法,为在对象方法中读写对象内部状态提供了途径:
class Person { private name: string; constructor(name: string) { this.name = name; } public introduce(): void { console.log(`Hello, my name is ${this.name}.`); } } const person1 = new Person('Alice'); person1.introduce(); // Hello, my name is Alice.需要留意的是,this的绑定取决于方法被调用的方式;将方法作为回调传递(如setTimeout(person.introduce))时可能丢失正确的this指向,需要结合箭头函数或bind处理,不过文档正文未展开这一点,在此仅作提示。
七、参数属性(Parameter Properties)
参数属性允许在构造函数参数中直接声明并初始化类属性,从而避免样板代码。例如:
class Person { constructor( private name: string, public age: number ) { // The "private" and "public" keywords in the constructor // automatically declare and initialize the corresponding class properties. } public introduce(): void { console.log( `Hello, my name is ${this.name} and I am ${this.age} years old.` ); } } const person = new Person('Alice', 25); person.introduce();构造函数参数前的private/public/protected/readonly等修饰符会自动声明并初始化同名类属性,省去了「先声明字段、再在构造函数中逐行赋值」的重复劳动。这也是后文「静态成员」示例中constructor(private name: string)的写法依据。
八、抽象类(Abstract Classes)
抽象类在 TypeScript 中主要用于继承,它提供了一种定义可被子类继承的公共属性与方法的方式。当你希望定义公共行为并强制子类实现某些方法时非常有用,其核心价值在于构建「抽象基类提供共享接口与公共功能、子类负责具体实现」的类层级结构。
abstract class Animal { protected name: string; constructor(name: string) { this.name = name; } abstract makeSound(): void; } class Cat extends Animal { makeSound(): void { console.log(`${this.name} meows.`); } } const cat = new Cat('Whiskers'); cat.makeSound(); // Output: Whiskers meows.要点:abstract关键字修饰的类不能被直接实例化;abstract方法只有签名没有实现,任何非抽象子类都必须实现它,否则编译器会报错。本例中Cat必须提供makeSound的实现,同时继承了基类的name属性(protected,仅子类可访问)。
九、与泛型结合使用(Classes with Generics)
泛型类允许定义可针对不同类型工作的可复用类:
class Container<T> { private item: T; constructor(item: T) { this.item = item; } getItem(): T { return this.item; } setItem(item: T): void { this.item = item; } } const container1 = new Container<number>(42); console.log(container1.getItem()); // 42 const container2 = new Container<string>('Hello'); container2.setItem('World'); console.log(container2.getItem()); // World类型参数T在类实例化时被具体化:Container<number>中getItem()返回number,Container<string>中则返回string,类型安全贯穿整个容器生命周期,避免了any带来的运行时类型隐患。
十、装饰器(Decorators)
装饰器提供了一种为目标元素添加元数据、修改行为、进行校验或扩展功能的机制。它们是在运行时执行的函数,且一个声明上可以应用多个装饰器。
10.1 兼容性与启用方式
需要特别注意版本约束:
- 装饰器是实验性特性,本文及书中的示例仅兼容 TypeScript 5 及以上版本(配合 ES6);
- 对于5 之前的 TypeScript 版本,需要在 tsconfig.json 中启用
experimentalDecorators属性,或在命令行中使用--experimentalDecorators(但书中的示例在这种模式下不会工作); - 注意:TypeScript 5 的装饰器不允许装饰参数(parameter)。
项目当前环境也印证了这一点:网站工程 website/package.json 依赖typescript: ^5.9.3,工具工程 tools/package.json 依赖typescript: ^5.4.5,且 tools/tsconfig.json 的lib中包含了"esnext.decorators",说明整套文档与示例都是基于新版(Stage 3)装饰器语义编写的。
装饰器的常见用途包括:
- 监听属性变化;
- 监听方法调用;
- 添加额外属性或方法;
- 运行时校验;
- 自动序列化与反序列化;
- 日志记录;
- 权限控制与认证;
- 错误防护。
装饰器按目标可分为:类装饰器、属性装饰器、方法装饰器、getter/setter 装饰器,以及配套的装饰器元数据机制。
10.2 类装饰器(Class Decorators)
类装饰器适用于扩展已有类,例如添加属性或方法、收集类实例等。下面这个例子为类添加了toString能力——实际上是通过返回一个包装类,在构造时打印实例与上下文信息:
type Constructor<T = {}> = new (...args: any[]) => T; function toString<Class extends Constructor>( Value: Class, context: ClassDecoratorContext<Class> ) { return class extends Value { constructor(...args: any[]) { super(...args); console.log(JSON.stringify(this)); console.log(JSON.stringify(context)); } }; } @toString class Person { name: string; constructor(name: string) { this.name = name; } greet() { return 'Hello, ' + this.name; } } const person = new Person('Simon'); /* Logs: {"name":"Simon"} {"kind":"class","name":"Person"} */注意新版装饰器函数的签名:第一个参数是目标(类装饰器为目标类构造函数),第二个参数是上下文对象context,其中kind和name描述了被装饰元素。
10.3 属性装饰器(Property Decorators)
属性装饰器用于修改属性行为,例如改变初始化值。下面的代码让属性值在赋值时始终转为大写:
function upperCase<T>( target: undefined, context: ClassFieldDecoratorContext<T, string> ) { return function (this: T, value: string) { return value.toUpperCase(); }; } class MyClass { @upperCase prop1 = 'hello!'; } console.log(new MyClass().prop1); // Logs: HELLO!属性(字段)装饰器返回的替换函数会在字段初始化时被调用,入参为初始值,返回值成为最终存储值,因此'hello!'被改写为'HELLO!'。
10.4 方法装饰器(Method Decorators)
方法装饰器允许改变或增强方法行为。下面是一个简单的日志器示例:
function log<This, Args extends any[], Return>( target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext< This, (this: This, ...args: Args) => Return > ) { const methodName = String(context.name); function replacementMethod(this: This, ...args: Args): Return { console.log(`LOG: Entering method '${methodName}'.`); const result = target.call(this, ...args); console.log(`LOG: Exiting method '${methodName}'.`); return result; } return replacementMethod; } class MyClass { @log sayHello() { console.log('Hello!'); } } new MyClass().sayHello();运行输出如下:
LOG: Entering method 'sayHello'. Hello! LOG: Exiting method 'sayHello'.装饰器返回的replacementMethod包装了原方法,通过target.call(this, ...args)保留正确的this上下文,实现进入/退出的日志拦截。
10.5 getter 与 setter 装饰器(Getter and Setter Decorators)
访问器装饰器允许改变或增强类访问器的行为,例如用于校验属性赋值。下面是一个 getter 装饰器示例,校验取值必须在给定区间内:
function range<This, Return extends number>(min: number, max: number) { return function ( target: (this: This) => Return, context: ClassGetterDecoratorContext<This, Return> ) { return function (this: This): Return { const value = target.call(this); if (value < min || value > max) { throw 'Invalid'; } Object.defineProperty(this, context.name, { value, enumerable: true, }); return value; }; }; } class MyClass { private _value = 0; constructor(value: number) { this._value = value; } @range(1, 100) get getValue(): number { return this._value; } } const obj = new MyClass(10); console.log(obj.getValue); // Valid: 10 const obj2 = new MyClass(999); console.log(obj2.getValue); // Throw: Invalid!range(1, 100)是一个装饰器工厂——它先接收参数返回真正的装饰器,装饰器再包装 getter,在取值时校验范围,越界即抛出'Invalid'。
10.6 装饰器元数据(Decorator Metadata)
装饰器元数据简化了装饰器在任意类中应用与利用元数据的过程。装饰器可以访问上下文对象上新增的metadata属性,该属性可作为原始值与对象的键;元数据信息可通过Symbol.metadata在类上获取。元数据可用于调试、序列化或基于装饰器的依赖注入等场景。
//@ts-ignore Symbol.metadata ??= Symbol('Symbol.metadata'); // Simple polyfill type Context = | ClassFieldDecoratorContext | ClassAccessorDecoratorContext | ClassMethodDecoratorContext; // Context contains property metadata: DecoratorMetadata function setMetadata(_target: any, context: Context) { // Set the metadata object with a primitive value context.metadata[context.name] = true; } class MyClass { @setMetadata a = 123; @setMetadata accessor b = 'b'; @setMetadata fn() {} } const metadata = MyClass[Symbol.metadata]; // Get metadata information console.log(JSON.stringify(metadata)); // {"bar":true,"baz":true,"foo":true}该示例展示了:同一个setMetadata装饰器可以同时作用于字段(a)、自动访问器(accessor b)与方法(fn),每个被装饰成员的名字都会作为键写入共享的metadata对象,最终通过MyClass[Symbol.metadata]统一读取。示例开头的Symbol.metadata ??= ...是一个简易 polyfill,用于在尚未原生支持该符号的环境中兜底(代码块前的//@ts-ignore用于抑制严格模式下的类型告警)。
十一、继承(Inheritance)
继承指一个类可以从另一个类(基类或超类)继承属性与方法。派生类(也叫子类)可以通过添加新属性、新方法或重写已有成员来扩展和特化基类功能:
class Animal { name: string; constructor(name: string) { this.name = name; } speak(): void { console.log('The animal makes a sound'); } } class Dog extends Animal { breed: string; constructor(name: string, breed: string) { super(name); this.breed = breed; } speak(): void { console.log('Woof! Woof!'); } } // Create an instance of the base class const animal = new Animal('Generic Animal'); animal.speak(); // The animal makes a sound // Create an instance of the derived class const dog = new Dog('Max', 'Labrador'); dog.speak(); // Woof! Woof!"要点:
- 派生类构造函数必须先调用
super(...)完成基类初始化,才能访问this; Dog.speak()重写了基类的speak(),实现了方法覆盖(override)。
11.1 单继承与多接口实现
TypeScript不支持传统意义上的多重继承,一个类只能继承自一个基类;但 TypeScript 支持多个接口——接口可以定义对象结构的契约,一个类可以implements多个接口,从而从多个来源继承行为与结构:
interface Flyable { fly(): void; } interface Swimmable { swim(): void; } class FlyingFish implements Flyable, Swimmable { fly() { console.log('Flying...'); } swim() { console.log('Swimming...'); } } const flyingFish = new FlyingFish(); flyingFish.fly(); flyingFish.swim();11.2 class 的本质:语法糖
与 JavaScript 一样,TypeScript 中的class关键字常被称为语法糖。它在 ECMAScript 2015(ES6)中引入,为以类的方式创建和操作对象提供了更熟悉的语法。但必须注意:TypeScript 是 JavaScript 的超集,最终会被编译为 JavaScript,而 JavaScript 的核心依然是**基于原型(prototype-based)**的——类本质上仍是构造函数与原型链的封装。
十二、静态成员(Statics)
TypeScript 支持静态成员。访问类的静态成员时无需创建对象,直接用「类名.成员名」即可:
class OfficeWorker { static memberCount: number = 0; constructor(private name: string) { OfficeWorker.memberCount++; } } const w1 = new OfficeWorker('James'); const w2 = new OfficeWorker('Simon'); const total = OfficeWorker.memberCount; console.log(total); // 2static memberCount属于类本身而非实例:每创建一个OfficeWorker实例,构造函数就使memberCount自增一次,最终通过OfficeWorker.memberCount读出总人数 2。静态成员适合存放与类相关的全局计数、配置常量或工具方法。
十三、属性初始化(Property Initialization)
TypeScript 中初始化类属性有几种方式。
内联(Inline)初始化——实例创建时使用这些初始值:
class MyClass { property1: string = 'default value'; property2: number = 42; }在构造函数中初始化:
class MyClass { property1: string; property2: number; constructor() { this.property1 = 'default value'; this.property2 = 42; } }使用构造函数参数(参数属性 + 默认值):
class MyClass { constructor( private property1: string = 'default value', public property2: number = 42 ) { // There is no need to assign the values to the properties explicitly. } log() { console.log(this.property2); } } const x = new MyClass(); x.log();第三种方式把「声明、初始化、默认值」三件事合并到构造参数中:private property1: string = 'default value'既声明了属性又设定了默认值,无需在构造体内显式赋值。需要说明的是,在strict模式下(本项目 tsconfig.json 与 compile.ts 均启用strict),类属性若未初始化且无默认值会触发「属性没有初始化器」的告警,因此文档中列出的三种方式也是消除该类告警的标准做法。
十四、方法重载(Method Overloading)
方法重载允许一个类拥有同名但参数类型或参数数量不同的多个方法,从而根据传入参数以不同方式调用同一方法:
class MyClass { add(a: number, b: number): number; // Overload signature 1 add(a: string, b: string): string; // Overload signature 2 add(a: number | string, b: number | string): number | string { if (typeof a === 'number' && typeof b === 'number') { return a + b; } if (typeof a === 'string' && typeof b === 'string') { return a.concat(b); } throw new Error('Invalid arguments'); } } const r = new MyClass(); console.log(r.add(10, 5)); // Logs 15与构造函数重载类似:先声明两个重载签名((number, number): number与(string, string): string),再提供兼容所有签名的实现签名(参数类型取联合类型number | string)。实现内部通过typeof类型守卫区分数字相加与字符串拼接,参数组合不合法时抛出异常。重载让调用方获得精确的返回类型推断——r.add(10, 5)的类型是number而非number | string。
结语
本文围绕 The Concise TypeScript Book 的 Class 章节,完整覆盖了 TypeScript 类的 14 个核心主题:基础语法、构造函数及其重载、私有/受保护构造函数、访问修饰符、getter/setter、自动访问器、this、参数属性、抽象类、泛型类、六类装饰器及其元数据机制、继承与多接口实现、静态成员、属性初始化和方法重载。文中所有代码均可直接复制运行,且大部分经过项目工具链(compile.ts、verify_codeblocks.py、tsconfig.json)在strict模式下的真实编译校验。若需继续深入,可参考同仓库的其他章节文档,例如 访问修饰符与接口、泛型 与 类型操作,按主题循序渐进地构建完整的 TypeScript 知识体系。
- 文档
- 教程
【免费下载链接】typescript-book
The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.
相关推荐
如何配置 CesiumJS 在完全离线环境中运行本地影像与 3D Tiles 数据?
如何配置 CesiumJS 在完全离线环境中运行本地影像与 3D Tiles 数据? 默认情况下,CesiumJS 在运行时会使用多个需要联网的外部数据源: V
文档教程TypeScript 类完全指南:从基础语法到装饰器,The Concise TypeScript Book 实战解读
TypeScript 类完全指南:从基础语法到装饰器,The Concise TypeScript Book 实战解读 导读 :本文以开源项目 The Conc
文档教程TypeScript 类(Class)深度指南:从基础语法到装饰器实战 —— 基于 The Concise TypeScript Book
TypeScript 类(Class)深度指南:从基础语法到装饰器实战 —— 基于 The Concise TypeScript Book 本指南以 The C
文档教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考