TypeScript 是 JavaScript 的超集,它通过静态类型检查为大型应用提供更好的可维护性和可扩展性。在掌握了基础类型、接口和泛型之后,进阶的学习将深入到更复杂的类型操作和高级特性。
1. 索引签名与索引类型
想象一下,你正在管理一个图书馆的书籍目录系统。在这个系统中,每本书都有一个唯一的索引号,通过这个索引号,你不仅可以查找到书的具体位置,还能获取到书的其他信息,如作者、出版年份等。这个索引号与书籍信息的对应关系,可以类比为索引签名与索引类型。
在TypeScript中,索引签名允许我们定义一个对象,其中的属性可以通过特定类型的键来访问。这就像图书馆中的书籍目录,通过索引号(键)可以快速定位到书籍(值)。
代码示例:
type StringMap = {
[key: string]: string;
};
const user: StringMap = {
name: 'Alice',
age: '25' // 这里实际上应该是一个字符串,但为了演示,我们使用了数字的字符串表示
};
function logValue(obj: StringMap, key: string) {
console.log(obj[key]);
}
logValue(user, 'name'); // 输出 "Alice"
在这个例子中,StringMap 是一个索引类型,它表示一个对象,其中的键是字符串,值也是字符串。
2. 类型别名与递归类型
类型别名可以类比为给一个复杂的食谱起一个简单易记的名字,比如“妈妈的特制蛋糕”。这个“特制蛋糕”的制作过程可能非常复杂,涉及多种原料和步骤,但有了这个别名,你只需要记住这个名字,就能知道它代表什么。递归类型则可以想象成一个“无限嵌套的俄罗斯套娃”,每个套娃里面都有一个更小的套娃,直到某个点为止。
在TypeScript中,类型别名用于给复杂类型起一个更简洁的名称,使得代码更易读。递归类型则允许我们定义可以自我引用的类型,这在处理如树形结构、嵌套数据等复杂数据结构时非常有用。
代码示例:
type TreeNode = {
value: number;
children?: TreeNode[];
};
const tree: TreeNode = {
value: 1,
children: [
{
value: 2,
children: [
{ value: 5 },
{ value: 6 }
]
},
{
value: 3,
children: [{ value: 7 }]
},
{ value: 4 }
]
};
在这个例子中,TreeNode 是一个递归类型,它定义了一个树节点,其中的 children 属性可以是 TreeNode 的数组。
3.TypeScript 装饰器
TypeScript 的注解(或称为装饰器)是一个非常强大的特性,尽管它目前仍处于实验阶段,但装饰器提供了一种在类声明、方法、属性或参数级别上添加元数据或修改类行为的方式。这在实现诸如权限检查、性能监控、日志记录、自动序列化/反序列化等场景中非常有用。
3.1. 函数装饰器
想象一下,你正在准备一场派对,需要在入口处设置一个“检查站”,确保每位客人在进入前都已经签到。这个“检查站”可以视为一个装饰器,它在原有功能(进入派对)之前添加了额外的步骤(签到)。
函数装饰器在不修改原函数代码的情况下,为其添加额外的功能,比如记录函数的执行时间、验证参数等。它接收三个参数:对于实例方法和静态方法,前两个参数分别是类的构造函数和属性的键名,第三个参数是属性描述符。
代码示例:
// 定义一个日志装饰器,用于记录方法调用
function log(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
// descriptor.value 指向原始方法
const originalMethod = descriptor.value;
// 替换方法实现,添加日志记录逻辑
descriptor.value = function (...args: any[]) {
// 输出方法调用信息
console.log(`Calling method ${propertyKey}`);
// 调用原始方法,并传递参数
return originalMethod.apply(this, args);
};
// 返回修改后的描述符
return descriptor;
}
// 定义一个包含装饰器方法的类
class Math {
// 使用 @log 装饰器修饰 add 方法
@log
add(a: number, b: number): number {
return a + b;
}
}
// 创建 Math 类的实例
const math = new Math();
// 调用 add 方法,将触发日志装饰器
math.add(1, 2); // 输出 "Calling method add"
target:这是类的原型对象。在类方法装饰器中,target指向包含装饰方法的类的原型。propertyKey:这是被装饰方法的名称,以字符串形式给出。descriptor:这是属性描述符对象,包含了被装饰方法的详细信息。descriptor.value指向原始方法。
通过修改descriptor.value,我们可以改变方法的实现,从而添加额外的功能,如日志记录。最终,通过return descriptor返回修改后的描述符,以确保装饰器能够正确地应用到方法上。这种方法不仅能够增强方法的功能,而且能够保持原有方法的清晰性和可读性。
3.2. 类装饰器
类装饰器可以类比为一家公司为员工定制的制服。每位员工都有自己的职责和特性,但通过统一的制服,公司可以确保所有员工在外观上符合公司的形象标准。制服(类装饰器)在不改变员工(类)本质功能的前提下,为其添加了一层统一的外观或行为。
类装饰器应用于类构造函数,可以在类定义时,自动为类添加或修改属性和方法,实现对类的增强。
代码示例:
// 定义一个序列化装饰器,为类添加序列化方法
function serializable(target: Function) {
// target 是被装饰的类的构造函数
// 通过原型链为类添加序列化方法
target.prototype.serialize = function () {
// 使用 JSON.stringify 将对象转换为 JSON 字符串
return JSON.stringify(this);
};
}
// 使用 @serializable 装饰器修饰 User 类
@serializable
class User {
// 类的属性
constructor(public name: string, public age: number) {}
}
// 创建 User 类的实例
const user = new User('Alice', 25);
// 调用序列化方法,输出 JSON 字符串
console.log(user.serialize()); // 输出 '{"name":"Alice","age":25}'
target:这是类的构造函数。在类装饰器中,target指向被装饰类的构造函数。
通过target.prototype,我们可以向类的原型添加方法,从而为所有实例添加共享的功能。
3.3. 属性装饰器
假设你正在设计一款智能手表,其中有一项功能是显示步数。为了确保步数的准确性,你希望在每次更新步数时,都能自动校验和记录数据。这可以通过在“步数”属性上使用装饰器来实现,装饰器在获取或设置属性值时,自动执行一些额外的逻辑。
属性装饰器允许我们在访问或修改类的属性时,执行一些额外的操作,比如验证、日志记录、数据转换等,从而增强属性的功能。它接收三个参数:类的构造函数、属性的键名和描述符。
代码示例:
// 定义一个配置装饰器,用于设置属性的可配置性
function configurable(value: boolean) {
return function (target: any, key: string) {
// 获取属性的描述符
let descriptor = Object.getOwnPropertyDescriptor(target, key);
// 修改描述符的 configurable 属性
descriptor.configurable = value;
// 重新定义属性
Object.defineProperty(target, key, descriptor);
};
}
// 使用 @configurable(false) 装饰器修饰 Config 类的 name 属性
class Config {
@configurable(false)
public name: string = 'Alice';
}
// 创建 Config 类的实例
const config = new Config();
// 尝试删除 name 属性,由于其不可配置性,将抛出错误
// delete config.name; // TypeError: Cannot delete property 'name' of #
target:这是类的原型对象或类的构造函数,具体取决于装饰器是在类方法还是类属性上使用。
key:这是被装饰属性的名称,以字符串形式给出。
通过Object.getOwnPropertyDescriptor和Object.defineProperty,我们能够修改属性的描述符,从而改变其行为,如设置configurable属性为false,使得属性不可被删除或重新定义。这种方法提供了强大的灵活性,能够根据需要动态地调整属性的行为。