考察点分析

本题主要考察以下核心能力维度:

  1. TypeScript类型系统理解:准确区分readonly修饰符与const声明的适用场景及设计意图
  2. 语言特性对比能力:辨析编译时类型检查与运行时行为差异
  3. 类与接口机制:掌握类属性初始化规则及类型修饰符在接口/类型别名中的表现

具体技术评估点:

  • readonlyconst的作用域及可变性差异
  • 类属性初始化阶段限制的底层逻辑
  • 类型擦除对运行时类型约束的影响
  • 接口/类型别名中readonly的编译时检查特性

技术解析

关键知识点

类型系统约束 > 类初始化机制 > 编译时检查

原理剖析

  1. 初始化限制
    readonly属性强制在声明时或构造函数中初始化,这是为确保类实例的不变性契约。类实例化时需完成所有状态初始化,若允许在其他方法修改readonly属性,会破坏编译器的控制流分析。

  2. const与readonly差异

  • const:变量级不可变,块级作用域,声明时必须初始化(const a=1
  • readonly:属性级不可变,可在构造函数赋值(class A { readonly a=1 }
  1. 运行时不可变性
    TypeScript执行类型擦除,接口/类型中的readonly仅在编译时检查。运行时仍可通过类型断言或JS原生方法修改值:
  interface Person { readonly name: string }
const p = { name: 'Alice' } as Person;
(p as any).name = 'Bob'; // 运行时实际可修改
  

常见误区

  • 误认为readonly可替代深不可变数据结构
  • 混淆readonlyconst的作用对象(属性 vs 变量)
  • 错误假设接口readonly在运行时具有保护能力

问题解答

readonly属性强制在声明或构造函数初始化,是因类实例状态需在构造阶段完全确定。const用于变量绑定,而readonly修饰属性,二者作用维度不同。接口/类型别名中的readonly仅触发编译时检查,TypeScript通过类型擦除机制,不会在运行时添加保护层,实际值仍可能被修改。

关键差异对比:

特性constreadonly
作用对象变量类/接口属性
初始化时机声明时声明时或构造器
运行时保护有(JS约束)
类型系统层级值空间类型空间

解决方案

运行时不可变实现

  class User {
  // 声明时初始化
  readonly id: string = "default";

  // 构造器初始化
  readonly createdAt: Date;

  constructor() {
    this.createdAt = new Date();
  }
}

// 尝试修改将引发编译错误
const user = new User();
user.id = "new"; // Error: Cannot assign to 'id' 
  

深度不可变方案

  // 使用as const实现嵌套只读
const deepReadonlyObj = {
  prop: { key: 'value' }
} as const;

// deepReadonlyObj.prop.key = 'new'; // 编译错误
  

深度追问

如何实现真正运行时只读?

  • 使用Object.freeze()冻结对象,但仅浅层冻结

类型别名与接口中readonly的行为差异?

  • 类型别名中readonly可修饰索引签名,接口中不可重复声明属性

只读数组类型如何工作?

  • ReadonlyArray<T>移除所有变异方法(如push),编译时阻止修改

Last updated 06 Mar 2025, 13:07 +0800 . history