TypeScript中只读属性和不可变对象需区分编译期保护(readonly、as const)与运行时冻结(Object.freeze):readonly用于接口/类属性声明且需构造时初始化,as const固化字面量类型并递归只读,Object.freeze仅冻结第一层且需手动深层冻结。

在 TypeScript 中定义只读属性和不可变对象,核心是区分“编译期保护”和“运行时冻结”——前者靠 readonly 和 as const 实现类型层面的只读约束,后者靠 Object.freeze() 等 JS 原生方法实现实际行为上的不可变。
接口/类中用 readonly 声明只读属性
readonly 是 TypeScript 的编译时修饰符,仅阻止赋值操作被类型检查通过,不改变运行时行为。
- 在 interface 或 class 中直接加 readonly 前缀:
interface User { readonly id: number; name: string; }此时user.id = 100会报错,但user.name仍可修改 - 类中只读属性必须在声明时或构造函数内初始化:
class Config { readonly version = "1.0"; }后续任何对this.version的赋值都会被 TS 拒绝 - 注意:readonly 不递归作用于嵌套对象。比如
readonly profile: { name: string },profile 对象本身不能被替换,但profile.name仍可改
用 as const 创建不可变字面量对象
as const 是类型断言,适用于对象、数组、元组等字面量,它同时做到两件事:推断出最窄的字面量类型 + 所有属性自动设为 readonly。
- 普通对象字面量默认推断为宽松类型:
const obj = { host: "api.com", port: 80 }→ 类型是{ host: string; port: number } - 加 as const 后:
const obj = { host: "api.com", port: 80 } as const→ 类型变成{ readonly host: "api.com"; readonly port: 80 }此时obj.host = "new"编译报错,连属性值都被固化为字面量类型 - 适合场景:配置项、状态常量、路由路径映射表等明确不会变更的数据
运行时冻结对象:Object.freeze()
如果需要在 JS 运行时真正阻止修改(比如防止意外赋值或调试时误操作),要用原生方法:
立即学习“Java免费学习笔记(深入)”;
-
Object.freeze(obj)冻结对象第一层属性:不能增删改,但 不递归。嵌套对象仍可变 - 需深层冻结时,手动递归调用:
function deepFreeze(obj) { Object.keys(obj).forEach(k => typeof obj[k] === 'object' && obj[k] !== null && deepFreeze(obj[k])); return Object.freeze(obj); } - 注意:
Object.freeze()是运行时行为,TS 类型系统默认不知道它已冻结,所以建议配合 as const 或类型断言使用,例如:const config = Object.freeze({ a: 1 }) as const;
只读数组与 ReadonlyArray
数组的不可变性也有两种常用方式,语义不同:
-
readonly string[]:表示一个只读数组类型,允许.map、.filter等读取操作,禁止.push、.pop、索引赋值(如arr[0] = "x") -
ReadonlyArray<string>:是泛型接口,语义相同,更显式;不能用new ReadonlyArray()构造 -
as const用于字面量数组:const arr = ["a", "b"] as const→ 类型为readonly ["a", "b"],不仅只读,还固定长度和每个元素的具体值 - 赋值方向有约束:普通数组可赋给只读数组变量,反之不行(防止把只读视图当可变引用使用)


















