|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
TypeScript作为JavaScript的超集,为我们带来了静态类型检查和更强大的面向对象编程能力。在TypeScript的众多特性中,类属性是构建健壮、可维护应用程序的核心组成部分。类属性不仅存储对象的状态,还通过类型注解和修饰符提供了强大的类型安全保障。本文将深入探讨TypeScript类属性的各个方面,从基础定义到高级应用,帮助读者全面掌握这一核心概念,提升TypeScript编程技能。
TypeScript类属性基础
类属性的基本定义
在TypeScript中,类属性是类中声明的变量,用于存储对象的状态或数据。与JavaScript不同,TypeScript允许我们为属性指定类型,从而在编译阶段进行类型检查。
- class Person {
- name: string; // 声明一个字符串类型的属性
- age: number; // 声明一个数字类型的属性
- }
- let person = new Person();
- person.name = "Alice";
- person.age = 30;
复制代码
在上面的例子中,我们定义了一个Person类,它有两个属性:name和age。每个属性都有明确的类型注解,确保只能赋相应类型的值。
属性类型注解
TypeScript中的类属性可以使用任何有效的类型进行注解,包括基本类型、对象类型、数组类型、函数类型等。
- class Car {
- brand: string; // 字符串类型
- year: number; // 数字类型
- isElectric: boolean; // 布尔类型
- features: string[]; // 字符串数组类型
- engine: { type: string; power: number }; // 对象类型
- drive: (speed: number) => void; // 函数类型
- }
- let myCar = new Car();
- myCar.brand = "Tesla";
- myCar.year = 2022;
- myCar.isElectric = true;
- myCar.features = ["Autopilot", "Full Self-Driving"];
- myCar.engine = { type: "Electric", power: 400 };
- myCar.drive = (speed: number) => {
- console.log(`Driving at ${speed} mph`);
- };
复制代码
属性初始化
在TypeScript中,类属性可以在声明时初始化,也可以在构造函数中初始化。如果属性没有初始化,并且没有显式标记为可选属性,TypeScript会报错。
- class Product {
- // 声明时初始化
- id: number = Math.floor(Math.random() * 1000);
- category: string = "General";
-
- // 构造函数中初始化
- name: string;
- price: number;
-
- constructor(name: string, price: number) {
- this.name = name;
- this.price = price;
- }
- }
- const product = new Product("Laptop", 999);
- console.log(product.id); // 随机生成的ID
- console.log(product.category); // "General"
- console.log(product.name); // "Laptop"
- console.log(product.price); // 999
复制代码
TypeScript 4.0+ 还支持使用类属性初始化的简写形式,当构造函数参数与类属性同名时:
- class Employee {
- constructor(
- public name: string, // 自动创建并初始化name属性
- private salary: number, // 自动创建并初始化私有salary属性
- readonly department: string // 自动创建并初始化只读department属性
- ) {}
- }
- const employee = new Employee("John Doe", 50000, "Engineering");
- console.log(employee.name); // "John Doe"
- // console.log(employee.salary); // 错误:salary是私有的
- console.log(employee.department); // "Engineering"
- // employee.department = "HR"; // 错误:department是只读的
复制代码
属性修饰符
TypeScript提供了几种属性修饰符,用于控制属性的可访问性和可修改性。这些修饰符包括public、private、protected和readonly。
public
public是默认的修饰符,表示属性可以在任何地方被访问。即使不显式指定,所有属性默认都是public的。
- class Student {
- public studentId: number; // 显式声明为public
- name: string; // 默认为public
-
- constructor(studentId: number, name: string) {
- this.studentId = studentId;
- this.name = name;
- }
- }
- let student = new Student(12345, "Jane Smith");
- console.log(student.studentId); // 12345
- console.log(student.name); // "Jane Smith"
- student.name = "Jane Doe"; // 可以修改
复制代码
private
private修饰符表示属性只能在声明它的类内部访问。子类和类的外部都无法访问私有属性。
- class BankAccount {
- private balance: number;
-
- constructor(initialBalance: number) {
- this.balance = initialBalance;
- }
-
- public deposit(amount: number): void {
- if (amount > 0) {
- this.balance += amount;
- console.log(`Deposited $${amount}. New balance: $${this.balance}`);
- }
- }
-
- public getBalance(): number {
- return this.balance;
- }
- }
- let account = new BankAccount(1000);
- account.deposit(500); // Deposited $500. New balance: $1500
- console.log(account.getBalance()); // 1500
- // console.log(account.balance); // 错误:balance是私有的,无法在类外部访问
- // 尝试通过子类访问私有属性
- class SavingsAccount extends BankAccount {
- public showBalance(): void {
- // console.log(this.balance); // 错误:无法在子类中访问父类的私有属性
- }
- }
复制代码
protected
protected修饰符表示属性只能在声明它的类及其子类中访问。与private不同,protected属性允许在继承层次结构中访问。
- class Vehicle {
- protected brand: string;
-
- constructor(brand: string) {
- this.brand = brand;
- }
-
- protected getBrand(): string {
- return this.brand;
- }
- }
- class Car extends Vehicle {
- private model: string;
-
- constructor(brand: string, model: string) {
- super(brand);
- this.model = model;
- }
-
- public getInfo(): string {
- // 可以访问父类的protected属性和方法
- return `${this.brand} ${this.model}`;
- }
- }
- let myCar = new Car("Toyota", "Camry");
- console.log(myCar.getInfo()); // "Toyota Camry"
- // console.log(myCar.brand); // 错误:无法在类外部访问protected属性
复制代码
readonly
readonly修饰符表示属性只能在初始化时或构造函数中被赋值,之后不能修改。这类似于其他语言中的常量概念。
- class Circle {
- readonly radius: number;
- readonly pi: number = 3.14159;
-
- constructor(radius: number) {
- this.radius = radius;
- }
-
- getArea(): number {
- return this.pi * this.radius * this.radius;
- }
- }
- let circle = new Circle(5);
- console.log(circle.getArea()); // 78.53975
- // circle.radius = 10; // 错误:radius是只读的,不能修改
- // circle.pi = 3.14; // 错误:pi是只读的,不能修改
复制代码
静态属性
静态属性是属于类本身而不是类实例的属性。它们使用static关键字声明,可以通过类名直接访问,而不需要创建类的实例。
静态属性定义与使用
- class MathUtility {
- static PI: number = 3.14159;
-
- static calculateCircumference(radius: number): number {
- return 2 * this.PI * radius;
- }
- }
- // 直接通过类名访问静态属性和方法
- console.log(MathUtility.PI); // 3.14159
- console.log(MathUtility.calculateCircumference(5)); // 31.4159
- // 创建实例后无法访问静态属性
- const mathUtil = new MathUtility();
- // console.log(mathUtil.PI); // 错误:实例无法访问静态属性
复制代码
静态属性的应用场景
静态属性通常用于存储与类相关的常量、配置信息或计数器等。
- class User {
- private static userCount: number = 0;
- private static readonly MAX_USERS: number = 100;
-
- constructor(private name: string) {
- // 每次创建新用户时增加计数器
- User.userCount++;
-
- if (User.userCount > User.MAX_USERS) {
- throw new Error(`Maximum number of users (${User.MAX_USERS}) exceeded`);
- }
- }
-
- public static getUserCount(): number {
- return User.userCount;
- }
-
- public static getMaxUsers(): number {
- return User.MAX_USERS;
- }
- }
- console.log(User.getUserCount()); // 0
- const user1 = new User("Alice");
- const user2 = new User("Bob");
- console.log(User.getUserCount()); // 2
- try {
- // 尝试创建超过最大用户数的用户
- for (let i = 0; i < 100; i++) {
- new User(`User${i}`);
- }
- } catch (error) {
- console.error(error.message); // Maximum number of users (100) exceeded
- }
复制代码
属性访问器
TypeScript支持使用getter和setter(也称为访问器)来控制对类属性的访问和修改。这使得我们可以在属性被访问或修改时执行额外的逻辑。
getter和setter
- class Employee {
- private _salary: number = 0;
-
- // getter
- get salary(): number {
- return this._salary;
- }
-
- // setter
- set salary(newSalary: number) {
- if (newSalary < 0) {
- throw new Error("Salary cannot be negative");
- }
- this._salary = newSalary;
- }
- }
- const employee = new Employee();
- employee.salary = 5000; // 调用setter
- console.log(employee.salary); // 调用getter,输出5000
- try {
- employee.salary = -1000; // 抛出错误:Salary cannot be negative
- } catch (error) {
- console.error(error.message);
- }
复制代码
属性访问器的优势
属性访问器提供了几个优势:
1. 封装:隐藏内部实现细节,只暴露必要的接口。
2. 验证:在设置属性值时进行验证。
3. 计算属性:提供基于其他属性计算得出的值。
4. 通知机制:在属性变化时触发其他操作。
- class Rectangle {
- private _width: number = 0;
- private _height: number = 0;
-
- get width(): number {
- return this._width;
- }
-
- set width(value: number) {
- if (value <= 0) {
- throw new Error("Width must be positive");
- }
- this._width = value;
- this.logChange("width", value);
- }
-
- get height(): number {
- return this._height;
- }
-
- set height(value: number) {
- if (value <= 0) {
- throw new Error("Height must be positive");
- }
- this._height = value;
- this.logChange("height", value);
- }
-
- // 计算属性
- get area(): number {
- return this._width * this._height;
- }
-
- get perimeter(): number {
- return 2 * (this._width + this._height);
- }
-
- private logChange(property: string, value: number): void {
- console.log(`${property} changed to ${value}`);
- }
- }
- const rect = new Rectangle();
- rect.width = 10; // 输出:width changed to 10
- rect.height = 5; // 输出:height changed to 5
- console.log(rect.area); // 50
- console.log(rect.perimeter); // 30
复制代码
抽象类中的属性
抽象类是不能被实例化的类,它们通常作为基类,为子类提供共同的属性和方法。在抽象类中,我们可以定义抽象属性,这些属性在抽象类中没有实现,必须在子类中实现。
抽象属性定义
- abstract class Shape {
- // 抽象属性,没有初始化
- abstract color: string;
-
- // 普通属性,有初始化
- readonly id: number = Math.floor(Math.random() * 1000);
-
- // 抽象方法
- abstract getArea(): number;
-
- // 普通方法
- getInfo(): string {
- return `Shape [ID: ${this.id}, Color: ${this.color}]`;
- }
- }
复制代码
抽象属性的实现
- class Circle extends Shape {
- // 实现抽象属性
- color: string;
- radius: number;
-
- constructor(color: string, radius: number) {
- super();
- this.color = color;
- this.radius = radius;
- }
-
- // 实现抽象方法
- getArea(): number {
- return Math.PI * this.radius * this.radius;
- }
- }
- class Rectangle extends Shape {
- // 实现抽象属性
- color: string;
- width: number;
- height: number;
-
- constructor(color: string, width: number, height: number) {
- super();
- this.color = color;
- this.width = width;
- this.height = height;
- }
-
- // 实现抽象方法
- getArea(): number {
- return this.width * this.height;
- }
- }
- const circle = new Circle("Red", 5);
- console.log(circle.getInfo()); // Shape [ID: 随机数, Color: Red]
- console.log(circle.getArea()); // 78.539...
- const rectangle = new Rectangle("Blue", 4, 6);
- console.log(rectangle.getInfo()); // Shape [ID: 随机数, Color: Blue]
- console.log(rectangle.getArea()); // 24
复制代码
类属性与接口
接口在TypeScript中用于定义对象的结构,包括类应该具有的属性和方法。类可以实现一个或多个接口,确保它们具有接口中定义的所有属性。
接口定义类属性
- interface IPerson {
- name: string;
- age: number;
- email?: string; // 可选属性
- readonly id: number; // 只读属性
- }
- class Person implements IPerson {
- name: string;
- age: number;
- email?: string;
- readonly id: number;
-
- constructor(id: number, name: string, age: number, email?: string) {
- this.id = id;
- this.name = name;
- this.age = age;
- this.email = email;
- }
- }
- const person = new Person(1, "John", 30, "john@example.com");
- console.log(person.name); // "John"
- // person.id = 2; // 错误:id是只读的
复制代码
类实现接口属性
一个类可以实现多个接口,每个接口可能定义不同的属性集。
- interface IIdentifiable {
- id: number;
- getId(): number;
- }
- interface ILoggable {
- log(): void;
- }
- class Product implements IIdentifiable, ILoggable {
- // 实现IIdentifiable接口
- id: number;
-
- // Product类自己的属性
- name: string;
- price: number;
-
- constructor(id: number, name: string, price: number) {
- this.id = id;
- this.name = name;
- this.price = price;
- }
-
- // 实现IIdentifiable接口的方法
- getId(): number {
- return this.id;
- }
-
- // 实现ILoggable接口的方法
- log(): void {
- console.log(`Product [ID: ${this.id}, Name: ${this.name}, Price: $${this.price}]`);
- }
- }
- const product = new Product(101, "Laptop", 999.99);
- console.log(product.getId()); // 101
- product.log(); // Product [ID: 101, Name: Laptop, Price: $999.99]
复制代码
高级属性模式
TypeScript提供了一些高级的属性模式,使类属性的定义更加灵活和强大。
可选属性
使用?标记的属性是可选的,可以在类实例中存在或不存在。
- class UserProfile {
- username: string;
- email: string;
- age?: number; // 可选属性
- bio?: string; // 可选属性
-
- constructor(username: string, email: string, age?: number, bio?: string) {
- this.username = username;
- this.email = email;
- this.age = age;
- this.bio = bio;
- }
-
- displayInfo(): void {
- console.log(`Username: ${this.username}, Email: ${this.email}`);
- if (this.age !== undefined) {
- console.log(`Age: ${this.age}`);
- }
- if (this.bio !== undefined) {
- console.log(`Bio: ${this.bio}`);
- }
- }
- }
- const user1 = new UserProfile("alice", "alice@example.com");
- const user2 = new UserProfile("bob", "bob@example.com", 25, "Software developer");
- user1.displayInfo();
- // 输出:
- // Username: alice, Email: alice@example.com
- user2.displayInfo();
- // 输出:
- // Username: bob, Email: bob@example.com
- // Age: 25
- // Bio: Software developer
复制代码
只读属性
如前所述,readonly修饰符用于创建只读属性,但还有一些其他方式可以创建只读属性。
- class Configuration {
- // 使用readonly修饰符
- readonly appName: string = "MyApp";
-
- // 使用getter但不提供setter
- private _version: string = "1.0.0";
- get version(): string {
- return this._version;
- }
-
- // 使用Object.defineProperty
- private _apiKey: string = "secret-key";
-
- constructor() {
- // 使用Object.defineProperty创建只读属性
- Object.defineProperty(this, 'apiKey', {
- value: this._apiKey,
- writable: false,
- enumerable: true,
- configurable: false
- });
- }
- }
- const config = new Configuration();
- console.log(config.appName); // "MyApp"
- console.log(config.version); // "1.0.0"
- console.log(config.apiKey); // "secret-key"
- // 尝试修改只读属性
- // config.appName = "NewApp"; // 错误:appName是只读的
- // config.version = "2.0.0"; // 错误:没有setter
- // config.apiKey = "new-key"; // 错误:apiKey是只读的
复制代码
计算属性名
TypeScript允许使用表达式作为属性名,这在创建动态属性时非常有用。
- class DynamicProperties {
- // 使用计算属性名
- [propertyName: string]: any;
-
- constructor() {
- // 动态设置属性
- this["id"] = 123;
- this["name"] = "Dynamic Object";
-
- // 使用变量作为属性名
- const propName = "createdAt";
- this[propName] = new Date();
- }
- }
- const dynamicObj = new DynamicProperties();
- console.log(dynamicObj.id); // 123
- console.log(dynamicObj.name); // "Dynamic Object"
- console.log(dynamicObj.createdAt); // 当前日期时间
- // 添加更多动态属性
- dynamicObj["updatedAt"] = new Date();
- console.log(dynamicObj.updatedAt); // 当前日期时间
复制代码
属性装饰器
装饰器是一种特殊的声明,可以附加到类声明、方法、属性或参数上,以修改类的行为。属性装饰器用于观察、修改或替换属性定义。
装饰器基础
要使用装饰器,必须在tsconfig.json中启用experimentalDecorators选项:
- {
- "compilerOptions": {
- "experimentalDecorators": true
- }
- }
复制代码
自定义属性装饰器
- // 属性装饰器函数
- function logProperty(target: any, propertyKey: string) {
- // 保存原始属性的值
- let value: any;
-
- // 属性getter
- const getter = function() {
- console.log(`Get: ${propertyKey} => ${value}`);
- return value;
- };
-
- // 属性setter
- const setter = function(newVal: any) {
- console.log(`Set: ${propertyKey} => ${newVal}`);
- value = newVal;
- };
-
- // 替换属性
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- }
- // 只读属性装饰器
- function readonly(target: any, propertyKey: string) {
- Object.defineProperty(target, propertyKey, {
- writable: false,
- enumerable: true,
- configurable: true
- });
- }
- class Example {
- @logProperty
- public name: string;
-
- @readonly
- public version: string = "1.0.0";
-
- constructor(name: string) {
- this.name = name;
- }
- }
- const example = new Example("Test");
- // 输出:Set: name => Test
- const nameValue = example.name;
- // 输出:Get: name => Test
- example.name = "Updated";
- // 输出:Set: name => Updated
- console.log(example.version); // "1.0.0"
- // example.version = "2.0.0"; // 错误:version是只读的
复制代码
更复杂的属性装饰器
下面是一个更复杂的属性装饰器示例,用于验证属性值:
- // 验证装饰器工厂
- function validate(validationFn: (value: any) => boolean, errorMessage: string) {
- return function(target: any, propertyKey: string) {
- let value: any;
-
- const getter = function() {
- return value;
- };
-
- const setter = function(newVal: any) {
- if (!validationFn(newVal)) {
- throw new Error(errorMessage);
- }
- value = newVal;
- };
-
- Object.defineProperty(target, propertyKey, {
- get: getter,
- set: setter,
- enumerable: true,
- configurable: true
- });
- };
- }
- class User {
- @validate(
- value => typeof value === 'string' && value.length >= 3,
- "Username must be at least 3 characters long"
- )
- public username: string;
-
- @validate(
- value => typeof value === 'number' && value >= 18 && value <= 120,
- "Age must be between 18 and 120"
- )
- public age: number;
-
- @validate(
- value => typeof value === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),
- "Invalid email format"
- )
- public email: string;
-
- constructor(username: string, age: number, email: string) {
- this.username = username;
- this.age = age;
- this.email = email;
- }
- }
- // 有效用户
- const validUser = new User("john_doe", 30, "john@example.com");
- console.log(validUser.username); // "john_doe"
- try {
- // 无效用户名
- const invalidUser1 = new User("jd", 30, "john@example.com");
- } catch (error) {
- console.error(error.message); // Username must be at least 3 characters long
- }
- try {
- // 无效年龄
- const invalidUser2 = new User("john_doe", 15, "john@example.com");
- } catch (error) {
- console.error(error.message); // Age must be between 18 and 120
- }
- try {
- // 无效邮箱
- const invalidUser3 = new User("john_doe", 30, "invalid-email");
- } catch (error) {
- console.error(error.message); // Invalid email format
- }
复制代码
类属性的最佳实践
命名约定
遵循一致的命名约定可以提高代码的可读性和可维护性:
- class UserProfile {
- // 使用驼峰命名法命名普通属性
- firstName: string;
- lastName: string;
- dateOfBirth: Date;
-
- // 私有属性可以使用下划线前缀
- private _apiKey: string;
-
- // 常量属性使用全大写和下划线
- static readonly MAX_LOGIN_ATTEMPTS: number = 3;
-
- // 布尔属性使用is或has前缀
- isActive: boolean;
- hasPermission: boolean;
-
- constructor(firstName: string, lastName: string, dateOfBirth: Date) {
- this.firstName = firstName;
- this.lastName = lastName;
- this.dateOfBirth = dateOfBirth;
- this._apiKey = "default-api-key";
- this.isActive = true;
- this.hasPermission = false;
- }
- }
复制代码
属性组织
在类中组织属性可以提高代码的可读性:
- class Order {
- // 1. 静态属性
- static readonly MIN_ORDER_VALUE: number = 10;
- static readonly MAX_ITEMS: number = 100;
-
- // 2. 公共实例属性
- public id: string;
- public customerId: string;
- public items: OrderItem[];
-
- // 3. 受保护属性
- protected discountRate: number = 0;
-
- // 4. 私有属性
- private _createdAt: Date;
- private _updatedAt: Date;
- private _status: OrderStatus = OrderStatus.Pending;
-
- constructor(id: string, customerId: string, items: OrderItem[]) {
- this.id = id;
- this.customerId = customerId;
- this.items = items;
- this._createdAt = new Date();
- this._updatedAt = new Date();
- }
-
- // 方法...
- }
- enum OrderStatus {
- Pending,
- Processing,
- Shipped,
- Delivered,
- Cancelled
- }
- interface OrderItem {
- productId: string;
- quantity: number;
- price: number;
- }
复制代码
类型安全考虑
充分利用TypeScript的类型系统来增强属性的类型安全:
- // 使用联合类型
- type UserRole = 'admin' | 'editor' | 'viewer';
- class User {
- role: UserRole;
-
- constructor(role: UserRole) {
- this.role = role;
- }
- }
- const admin = new User('admin');
- // const invalidUser = new User('guest'); // 错误:'guest'不是有效的UserRole
- // 使用字面量类型
- type HttpStatus = 200 | 201 | 400 | 401 | 404 | 500;
- class ApiResponse {
- status: HttpStatus;
- data: any;
-
- constructor(status: HttpStatus, data: any) {
- this.status = status;
- this.data = data;
- }
- }
- const successResponse = new ApiResponse(200, { message: 'Success' });
- // const invalidResponse = new ApiResponse(300, {}); // 错误:300不是有效的HttpStatus
- // 使用泛型类
- class Repository<T> {
- private items: T[] = [];
-
- add(item: T): void {
- this.items.push(item);
- }
-
- getAll(): T[] {
- return [...this.items];
- }
- }
- interface Product {
- id: string;
- name: string;
- price: number;
- }
- const productRepository = new Repository<Product>();
- productRepository.add({ id: '1', name: 'Laptop', price: 999 });
- // productRepository.add({ id: '2', name: 'Phone' }); // 错误:缺少price属性
复制代码
总结
TypeScript类属性是构建类型安全和面向对象应用程序的核心组成部分。通过本文的详细探讨,我们了解了TypeScript类属性的各个方面:
1. 基础定义:类属性的基本定义、类型注解和初始化方式。
2. 属性修饰符:public、private、protected和readonly修饰符的使用和区别。
3. 静态属性:属于类本身而不是实例的属性,以及它们的应用场景。
4. 属性访问器:使用getter和setter控制属性访问和修改。
5. 抽象类中的属性:抽象属性的定义和实现。
6. 类属性与接口:接口如何定义类属性,以及类如何实现接口属性。
7. 高级属性模式:可选属性、只读属性和计算属性名。
8. 属性装饰器:使用装饰器观察、修改或替换属性定义。
9. 最佳实践:属性命名约定、组织方式和类型安全考虑。
掌握这些概念和技术,将帮助开发者充分利用TypeScript的类型系统和面向对象特性,构建更加健壮、可维护的应用程序。TypeScript类属性不仅提供了强大的类型安全保障,还通过修饰符、访问器和装饰器等特性,为开发者提供了灵活的工具来设计和实现复杂的类结构。
随着TypeScript的不断发展,类属性和相关特性也在不断演进。保持对最新版本特性的关注,并遵循最佳实践,将有助于开发者充分利用TypeScript的强大功能,提升代码质量和开发效率。 |
|