readonly 修饰符的作用
TypeScript 中的 readonly 修饰符主要用于将属性标记为不可变(immutable)。
一旦一个属性被标记为 readonly,它就只能在初始化时或在构造函数(Constructor)中被赋值。之后尝试修改该属性的值,TypeScript 编译器会报错。
以下是 readonly 的主要应用场景和细节:
1. 在接口(Interface)和类型别名(Type Alias)中使用
这是最常见的用法,用于约束对象结构,防止意外修改属性。
interface Point {
readonly x: number;
readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
// ✅ 读取是允许的
console.log(p1.x);
// ❌ 报错:无法分配到 "x" ,因为它是只读属性。
// p1.x = 5;
2. 在类(Class)中使用
在类中,readonly 属性只能在声明时或构造函数里赋值。
class Animal {
readonly name: string;
constructor(theName: string) {
// ✅ 在构造函数中赋值是允许的
this.name = theName;
}
changeName() {
// ❌ 报错:只能在构造函数中修改
// this.name = "Bob";
}
}
const cat = new Animal("Kitty");
// cat.name = "Tom"; // ❌ 报错
参数属性简写(Parameter Properties):
TypeScript 允许在构造函数参数中直接使用 readonly,这会自动创建同名的类属性并赋值,非常简洁。
class Person {
// 等同于声明了属性并赋值
constructor(readonly name: string) {
}
}
const p = new Person("Alice");
console.log(p.name); // Alice
// p.name = "Bob"; // ❌ 报错
3. 只读数组(ReadonlyArray)
如果你希望一个数组被创建后不能被修改(不能 push, pop, splice 等),可以使用 ReadonlyArray<T> 或 readonly T[]。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
// ro[0] = 12; // ❌ 报错
// ro.push(5); // ❌ 报错
// ro.length = 100; // ❌ 报错
// ✅ 但你可以把它重新赋值给一个普通数组(需要类型断言)
a = ro as number[];
4. Readonly<T> 工具类型
TypeScript 提供了一个内置的工具类型 Readonly<T>,它可以将类型 T 的所有属性都变为只读。
interface Todo {
title: string;
description: string;
}
const myTodo: Readonly<Todo> = {
title: "学习 TypeScript",
description: "掌握 readonly"
};
// myTodo.title = "睡觉"; // ❌ 报错:所有属性现在都是只读的
5. 关键细节与注意事项
A. 它是“浅层”只读 (Shallow Readonly)
readonly 只能确保属性本身的引用不被修改。如果该属性是一个对象,对象内部的属性依然是可以修改的。
interface Person {
readonly info: {
age: number;
};
}
const p: Person = { info: { age: 18 } };
// p.info = { age: 20 }; // ❌ 报错:不能修改 info 的引用
p.info.age = 20; // ✅ 允许!info 对象内部的属性没有被标记为 readonly
注:如果需要深层只读,通常需要结合递归类型或使用第三方库(如 DeepReadonly)。
B. const vs readonly
const:用于变量。防止变量被重新赋值。readonly:用于属性。防止对象的属性被修改。
const foo = { x: 1 };
foo.x = 2; // ✅ const 不限制对象内部属性的修改
interface Bar {
readonly x: number;
}
const bar: Bar = { x: 1 };
// bar.x = 2; // ❌ readonly 限制属性修改
C. 编译时检查 vs 运行时行为
readonly 只是 TypeScript 的编译时(Compile-time) 检查。代码编译成 JavaScript 后,readonly 关键字会被移除。这意味着在运行时(Runtime),如果你强行绕过类型检查(例如通过 any),依然可以修改该值。
如果要实现运行时的绝对只读,需要使用 JavaScript 原生的 Object.freeze()。
总结
使用 readonly 的主要目的是表达意图和提高代码安全性。它告诉其他开发者(以及编译器):这个属性初始化之后就不应该再变了,从而避免副作用和意外的 Bug。