只读属性
概述
只读属性(Readonly Properties)是 TypeScript 类中的一个重要特性,使用 readonly 关键字可以将类的属性标记为只读。只读属性只能在声明时或构造函数中初始化,之后不能被修改。这个特性有助于创建不可变的数据结构,提高代码的安全性和可维护性。
基本语法
声明只读属性
使用 readonly 关键字在类中声明只读属性:
typescript
class User {
readonly id: number;
name: string;
readonly createdAt: Date;
constructor(id: number, name: string) {
this.id = id; // 可以在构造函数中初始化
this.name = name;
this.createdAt = new Date(); // 可以在构造函数中初始化
}
}
const user = new User(1, "Alice");
console.log(user.id); // 1
console.log(user.createdAt); // 当前日期
// ❌ 错误:不能修改只读属性
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
// user.createdAt = new Date(); // Error声明时初始化
只读属性也可以在声明时直接初始化:
typescript
class Config {
readonly apiKey: string = "default-key";
readonly version: string = "1.0.0";
timeout: number = 5000; // 普通属性
constructor(apiKey?: string) {
if (apiKey) {
this.apiKey = apiKey; // 可以在构造函数中重新赋值
}
}
}
const config = new Config("custom-key");
console.log(config.apiKey); // "custom-key"
console.log(config.version); // "1.0.0"
// ❌ 错误:不能在构造函数外修改
// config.apiKey = "new-key"; // Error提示
只读属性可以在两个地方初始化:
- 声明时直接赋值
- 构造函数中赋值
一旦对象创建完成,只读属性就不能再被修改。
与访问修饰符组合使用
只读属性可以与访问修饰符(public、private、protected)组合使用:
public readonly(默认)
public readonly 是默认的访问级别,属性可以在外部读取但不能修改:
typescript
class Product {
public readonly id: number;
public readonly sku: string;
public name: string; // 可读可写
constructor(id: number, sku: string, name: string) {
this.id = id;
this.sku = sku;
this.name = name;
}
}
const product = new Product(1, "SKU-001", "Laptop");
console.log(product.id); // ✅ 可以读取
console.log(product.sku); // ✅ 可以读取
console.log(product.name); // ✅ 可以读取
product.name = "Desktop"; // ✅ 可以修改普通属性
// product.id = 2; // ❌ 错误:不能修改只读属性
// product.sku = "SKU-002"; // ❌ 错误:不能修改只读属性private readonly
private readonly 属性只能在类内部访问,且不能被修改:
typescript
class BankAccount {
private readonly accountNumber: string;
private balance: number;
constructor(accountNumber: string, initialBalance: number) {
this.accountNumber = accountNumber;
this.balance = initialBalance;
}
public getAccountNumber(): string {
return this.accountNumber; // ✅ 类内部可以访问
}
public deposit(amount: number): void {
this.balance += amount;
// this.accountNumber = "new-number"; // ❌ 错误:不能修改只读属性
}
}
const account = new BankAccount("ACC-12345", 1000);
console.log(account.getAccountNumber()); // "ACC-12345"
// ❌ 错误:外部不能访问 private 属性
// console.log(account.accountNumber); // Error: Property 'accountNumber' is privateprotected readonly
protected readonly 属性可以在类内部和子类中访问,但不能修改:
typescript
class Animal {
protected readonly species: string;
protected name: string;
constructor(species: string, name: string) {
this.species = species;
this.name = name;
}
public getInfo(): string {
return `${this.name} is a ${this.species}`;
}
}
class Dog extends Animal {
constructor(name: string) {
super("Canine", name);
}
public bark(): void {
console.log(`${this.name} (${this.species}) barks!`); // ✅ 子类可以访问
// this.species = "Feline"; // ❌ 错误:不能修改只读属性
}
}
const dog = new Dog("Buddy");
dog.bark(); // "Buddy (Canine) barks!"使用场景
场景 1:唯一标识符
只读属性非常适合用于唯一标识符,如用户 ID、订单号等:
typescript
class Order {
readonly orderId: string;
readonly customerId: number;
status: string;
total: number;
constructor(orderId: string, customerId: number, total: number) {
this.orderId = orderId;
this.customerId = customerId;
this.total = total;
this.status = "pending";
}
public updateStatus(newStatus: string): void {
this.status = newStatus; // ✅ 可以修改状态
// this.orderId = "new-id"; // ❌ 错误:订单 ID 不应该改变
}
public getOrderInfo(): string {
return `Order ${this.orderId} for customer ${this.customerId}: ${this.status}`;
}
}
const order = new Order("ORD-001", 123, 99.99);
console.log(order.getOrderInfo()); // "Order ORD-001 for customer 123: pending"
order.updateStatus("completed");
console.log(order.getOrderInfo()); // "Order ORD-001 for customer 123: completed"场景 2:时间戳
创建时间、更新时间等时间戳属性应该使用只读:
typescript
class Document {
readonly id: number;
readonly createdAt: Date;
title: string;
content: string;
updatedAt: Date;
constructor(id: number, title: string, content: string) {
this.id = id;
this.createdAt = new Date(); // 创建时间在对象创建时确定
this.updatedAt = new Date();
this.title = title;
this.content = content;
}
public updateContent(newContent: string): void {
this.content = newContent;
this.updatedAt = new Date(); // ✅ 可以更新修改时间
// this.createdAt = new Date(); // ❌ 错误:创建时间不应该改变
}
public getAge(): number {
const now = new Date();
return now.getTime() - this.createdAt.getTime();
}
}
const doc = new Document(1, "My Document", "Initial content");
console.log(`Document created at: ${doc.createdAt}`);
doc.updateContent("Updated content");
console.log(`Last updated at: ${doc.updatedAt}`);场景 3:配置对象
配置对象中的固定配置项应该使用只读:
typescript
class DatabaseConnection {
readonly host: string;
readonly port: number;
readonly database: string;
private connected: boolean = false;
constructor(host: string, port: number, database: string) {
this.host = host;
this.port = port;
this.database = database;
}
public connect(): void {
if (!this.connected) {
console.log(`Connecting to ${this.host}:${this.port}/${this.database}`);
this.connected = true;
}
}
public getConnectionString(): string {
return `${this.host}:${this.port}/${this.database}`;
}
}
const db = new DatabaseConnection("localhost", 5432, "mydb");
db.connect(); // "Connecting to localhost:5432/mydb"
// ❌ 错误:连接配置不应该改变
// db.host = "remote-host"; // Error场景 4:常量值
类中的常量值应该使用 readonly 和 static 组合:
typescript
class MathUtils {
static readonly PI: number = 3.14159;
static readonly E: number = 2.71828;
static readonly MAX_SAFE_INTEGER: number = Number.MAX_SAFE_INTEGER;
static circleArea(radius: number): number {
return MathUtils.PI * radius * radius;
}
}
console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.circleArea(5)); // 78.53975
// ❌ 错误:不能修改静态只读属性
// MathUtils.PI = 3.14; // Error只读属性与接口
只读属性在接口中也有类似的概念,但行为略有不同:
接口中的只读属性
typescript
interface User {
readonly id: number;
name: string;
}
const user: User = {
id: 1,
name: "Alice"
};
// ❌ 错误:不能修改接口中的只读属性
// user.id = 2; // Error类实现接口
类实现包含只读属性的接口时,需要使用 readonly 关键字:
typescript
interface Identifiable {
readonly id: number;
name: string;
}
class User implements Identifiable {
readonly id: number; // 必须使用 readonly
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
const user = new User(1, "Alice");
// user.id = 2; // ❌ 错误只读数组和对象
只读数组属性
如果属性是数组类型,readonly 只防止重新赋值,不防止修改数组内容:
typescript
class ShoppingCart {
readonly items: string[]; // 只读数组引用
constructor(items: string[] = []) {
this.items = items;
}
public addItem(item: string): void {
this.items.push(item); // ✅ 可以修改数组内容
}
public removeItem(item: string): void {
const index = this.items.indexOf(item);
if (index > -1) {
this.items.splice(index, 1); // ✅ 可以修改数组内容
}
}
// ❌ 错误:不能重新赋值整个数组
// public reset(): void {
// this.items = []; // Error: Cannot assign to 'items' because it is a read-only property
// }
}
const cart = new ShoppingCart(["Apple", "Banana"]);
cart.addItem("Orange");
console.log(cart.items); // ["Apple", "Banana", "Orange"]
// ❌ 错误:不能重新赋值
// cart.items = []; // Error使用 ReadonlyArray
如果需要真正的只读数组,可以使用 ReadonlyArray 类型:
typescript
class ReadOnlyList {
readonly items: ReadonlyArray<string>; // 只读数组类型
constructor(items: string[]) {
this.items = items;
}
public getItem(index: number): string | undefined {
return this.items[index]; // ✅ 可以读取
}
// ❌ 错误:不能修改只读数组
// public addItem(item: string): void {
// this.items.push(item); // Error: Property 'push' does not exist on type 'ReadonlyArray<string>'
// }
}
const list = new ReadOnlyList(["A", "B", "C"]);
console.log(list.getItem(0)); // "A"只读对象属性
对于对象类型的属性,readonly 只防止重新赋值,不防止修改对象内容:
typescript
class UserProfile {
readonly metadata: { version: number; created: Date };
constructor() {
this.metadata = {
version: 1,
created: new Date()
};
}
public updateVersion(): void {
this.metadata.version = 2; // ✅ 可以修改对象内容
// this.metadata = { version: 2, created: new Date() }; // ❌ 错误:不能重新赋值
}
}
const profile = new UserProfile();
profile.updateVersion();
console.log(profile.metadata.version); // 2类型检查示例
常见错误
typescript
// ❌ 错误:在构造函数外修改只读属性
class User {
readonly id: number;
constructor(id: number) {
this.id = id;
}
public changeId(newId: number): void {
// this.id = newId; // Error: Cannot assign to 'id' because it is a read-only property
}
}
const user = new User(1);
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property
// ❌ 错误:未初始化的只读属性
class Config {
readonly apiKey: string; // 错误:必须初始化
constructor() {
// 如果没有在构造函数中初始化,会报错
}
}
// Error: Property 'apiKey' has no initializer and is not definitely assigned in the constructor正确写法
typescript
// ✅ 正确:在构造函数中初始化
class User {
readonly id: number;
name: string;
constructor(id: number, name: string) {
this.id = id;
this.name = name;
}
}
const user = new User(1, "Alice");
console.log(user.id); // 1
// ✅ 正确:声明时初始化
class Config {
readonly apiKey: string = "default-key";
readonly version: string = "1.0.0";
}
const config = new Config();
console.log(config.apiKey); // "default-key"
// ✅ 正确:使用访问修饰符组合
class Product {
public readonly id: number;
private readonly sku: string;
constructor(id: number, sku: string) {
this.id = id;
this.sku = sku;
}
public getSku(): string {
return this.sku; // 类内部可以访问
}
}
const product = new Product(1, "SKU-001");
console.log(product.id); // ✅ 可以访问
console.log(product.getSku()); // ✅ 通过方法访问注意事项
提示
- 只读属性适合用于不会改变的值,如 ID、创建时间、配置项等
- 只读属性可以在声明时或构造函数中初始化
- 只读属性可以与访问修饰符(public、private、protected)组合使用
- 对于数组和对象类型的只读属性,
readonly只防止重新赋值,不防止修改内容 - 如果需要真正的不可变数组,使用
ReadonlyArray<T>类型
注意
readonly只在编译时生效,运行时仍然可以修改。如果需要真正的不可变对象,可以使用Object.freeze()或第三方库(如 Immutable.js)- 只读属性必须在声明时或构造函数中初始化,否则会报错
- 只读属性不能有 setter,但可以有 getter
- 对于数组和对象,
readonly只保护引用不被重新赋值,不保护内容不被修改
重要
- 只读属性是 TypeScript 编译时的特性,编译后的 JavaScript 代码中不会保留
readonly信息 - 如果需要运行时保护,必须使用
Object.freeze()或其他运行时机制 - 只读属性与
const关键字不同:const用于变量,readonly用于类属性
信息
只读属性的优势:
- 类型安全:编译时防止意外修改
- 代码清晰:明确标识不应该改变的值
- 维护性:减少因意外修改导致的 bug
适用场景:
- 唯一标识符(ID、订单号等)
- 时间戳(创建时间等)
- 配置项(连接信息、常量等)
- 不可变数据结构
相关链接
- 类基础 - 了解类的基本概念
- 访问修饰符 - 学习访问修饰符的使用
- 接口 - 了解接口中的只读属性
- 对象类型 - 学习对象类型中的只读属性
- TypeScript 官方文档 - 类