2026最新const readonly高频面试题,5个核心考点吃透
看了一堆教程还是不会写项目?别怪教程,是你没搞懂底层逻辑。2026最新的前端面试风向标已经变了,HR和面试官不再只问“是什么”,而是盯着“为什么”和“边界情况”不放。特别是 const 和 readonly 这两个词,看似基础,实则暗坑无数。很多开发者在面试时自信满满地说“const是常量”,结果被追问“那你为什么还能改属性?”瞬间破防。今天这篇干货,咱们不整虚的,直接拆解大厂真题,带你把这两个高频考点彻底吃透。
考点梳理:混淆即淘汰
在JavaScript和TypeScript的面试中,const 和 readonly 的混淆率高达40%以上。面试官抛出这个问题,核心目的只有一个:验证你对“引用类型”与“值类型”在内存中存储机制的理解,以及你对类型系统(TypeScript)的掌握程度。
很多候选人的回答停留在“const不能重新赋值,readonly不能修改属性”这种表层定义。这种回答在初级岗位或许能过,但在中高级岗位面前,直接判定为“不合格”。
我们需要厘清三个核心维度:作用域语言:const 是 JavaScript/ES6 原生关键字,readonly 是 TypeScript 特有修饰符。
约束对象:const 约束的是“变量绑定关系”,readonly 约束的是“属性可写性”。
生效时机:const 在运行时生效,readonly 主要在编译时生效(运行时依赖 Object.freeze 等手动处理或框架支持)。如果面试时被问到“const声明的对象,其属性可以被修改吗?”,答“不可以”就是典型的半吊子选手。正确答案应该是:const 保证的是变量指向的内存地址不变,但如果该地址存储的是对象或数组,其内部属性是可以被修改的。
标准答法:结构化表达
在面试现场,回答技术问题要有结构,不要东一句西一句。针对 const 和 readonly 的区别,建议采用“定义-机制-区别-场景”四步走策略。
第一步:明确定义
“const 是 ES6 引入的块级作用域声明关键字,用于声明只读变量引用。readonly 是 TypeScript 中的属性修饰符,用于声明对象属性的只读状态。”
第二步:阐述机制
“const 在运行时,将变量名与特定内存地址绑定。如果尝试重新赋值,引擎会抛出 TypeError。但对于对象类型,const 只锁定了地址,没锁定内容。readonly 则在编译阶段检查,任何对只读属性的赋值操作都会报编译错误。在运行时,TypeScript 类型会被擦除,readonly 本身不提供运行时保护,除非结合 Object.freeze 或使用支持运行时检查的框架。”
第三步:核心区别
“最大的区别在于作用层级。const 作用于变量声明,readonly 作用于对象属性。你可以用一个 const 声明一个 readonly 属性的对象,这是双重保险。”
第四步:应用场景
“在项目中,配置对象通常使用 const 声明,内部属性根据是否需要修改决定是否加 readonly。对于不可变数据结构,两者结合使用能最大化代码安全性。”
这种回答方式,既展示了对语言底层的理解,又体现了工程化的思维,面试官通常会给出高分。
代码实现:代码即真理
光说不练假把式,我们来看一段典型的面试题代码,并逐行拆解。
// 场景:定义一个用户配置对象,要求姓名不可变,地址可变interface UserConfig {readonly name: string;address: string;age: number;
}// 1. 使用 const 声明变量,绑定 UserConfig 类型
const user: UserConfig = {name: 'Alice',address: 'New York',age: 30
};// 2. 测试 const 的约束
// user = { name: 'Bob', address: 'London', age: 25 }; // ❌ 编译错误:不能给常量 'user' 赋值
// 运行时:TypeError: Assignment to constant variable.// 3. 测试 readonly 属性的约束
// user.name = 'Charlie'; // ❌ 编译错误:Cannot assign to 'name' because it is a read-only property.// 4. 测试非 readonly 属性
user.address = 'London'; // ✅ 允许,address 未标记为 readonly
user.age = 31; // ✅ 允许// 5. 陷阱:const 对象内部属性修改
const config = {theme: 'dark',settings: {fontSize: 14}
};config.settings.fontSize = 16; // ✅ 允许!const 不递归锁定
// config.theme = 'light'; // ❌ 编译错误:如果 config 被推断为 readonly 或显式声明为 readonly 对象字面量类型// 6. 进阶:Deep Readonly
type DeepReadonlyT = {readonly [P in keyof T]: T[P] extends object ? DeepReadonlyT[P] : T[P];
};const deepConfig: DeepReadonly{ a: { b: string } } = { a: { b: 'hello' } };
// deepConfig.a.b = 'world'; // ❌ 编译错误逐行讲解:接口定义:readonly name 表示 name 属性在类型层面是只读的。
变量声明:const user 锁定了 user 这个变量不能指向新的对象。
重新赋值错误:user = ... 会同时触发编译错误(TS)和运行时错误(JS),因为 const 禁止重新赋值。
属性修改错误:user.name = ... 会触发编译错误,因为 name 是 readonly。但注意,如果这段代码运行在纯 JS 环境中(没有 TS 编译),这行代码不会报错,name 会被修改。这就是 readonly 的局限性。
非只读属性:address 和 age 可以正常修改。
嵌套对象陷阱:这是面试重灾区。const 和 readonly 默认都是浅层的。config.settings.fontSize 的修改是完全合法的。如果要递归锁定,必须使用 DeepReadonly 或 Object.freeze 递归处理。
Deep Readonly:这是 TypeScript 4.x+ 的常见考点,考察你对类型体操的理解。追问与延伸:高手过招
当基础问题答完后,面试官通常会抛出追问,这时候就是拉开差距的时候。
追问1:const 声明的数组,可以 push 吗?
答:可以。const arr = [1, 2, 3]; arr.push(4); 是合法的。const 只锁定了 arr 这个引用,没锁定数组长度和内容。如果面试官说“那怎么防止修改?”,你要回答:“使用 Object.freeze(arr) 可以冻结数组,但这是浅冻结,嵌套对象依然可以改。如果需要深度冻结,需要递归 freeze。”
追问2:TypeScript 中 readonly 属性,在运行时真的不可变吗?
答:不真的。TypeScript 是静态类型语言,类型信息在编译后被擦除。readonly 只是给编译器看的,运行时它只是一个普通属性。如果直接操作 JS 代码,或者通过 any 类型绕过检查,都可以修改 readonly 属性。真正的运行时保护需要 Object.freeze 或 Object.defineProperty 配合。
追问3:const 和 let 在性能上有区别吗?
答:在 V8 引擎等现代 JS 引擎中,const 和 let 的性能差异微乎其微,可以忽略不计。但在某些极端的循环场景中,const 可能因为变量不可变,引擎能更好地进行内联缓存优化。不过,这不是主要选择依据,代码可读性和安全性才是。
追问4:在 React 或 Vue 中,const 有什么特殊意义吗?
答:在 React 中,函数组件必须使用函数声明或箭头函数,通常用 const 来定义组件,这是社区约定。因为 function 声明会提升,而 const 不会,但在模块化代码中,提升问题不大。更重要的是,const 强调组件引用的稳定性,符合 React 的纯函数理念。在 Vue 3 的 Composition API 中,const 用于声明 ref 和 reactive 对象,同样是为了避免意外重赋值。
追问5:如何生成一个真正的不可变对象?
答:结合 Object.freeze 和递归。
function deepFreeze(obj) {if (obj === null || typeof obj !== 'object') {return obj;}Object.freeze(obj);Object.getOwnPropertyNames(obj).forEach((name) = {const prop = obj[name];if (prop typeof prop === 'object' !Object.isFrozen(prop)) {deepFreeze(prop);}});return obj;
}这段代码在面试手写算法题中偶尔会出现,考察递归和对象遍历能力。
记忆口诀:考前速记
为了在紧张的面试中快速回忆起这些知识点,我总结了一个口诀:
Const 锁址不锁值,
Readonly 锁属不锁址。
JS 运行看 Const,
TS 编译看 Readonly。
浅层冻结有陷阱,
深层递归要深冻。
运行时擦除类型,
Freeze 才是真保护。
解释一下:Const 锁址不锁值:const 锁定的是变量指向的内存地址,不是地址里的数据值(对于对象)。
Readonly 锁属不锁址:readonly 锁定的是对象的属性,不锁定对象本身的引用。
JS 运行看 Const:在纯 JS 环境中,只有 const 有运行时效力,readonly 不存在。
TS 编译看 Readonly:在 TS 环境中,readonly 在编译期生效,提供类型安全。
浅层冻结有陷阱:默认的 const 和 readonly 都是浅层的,嵌套对象可改。
深层递归要深冻:需要不可变时,使用 DeepReadonly 类型或 deepFreeze 函数。
运行时擦除类型:TS 类型在运行时消失,readonly 无运行时保护。
Freeze 才是真保护:Object.freeze 才是运行时真正的只读保障。避坑指南:不要迷信 const:很多开发者以为用了 const 就是常量,结果在调试时发现对象属性被意外修改,排查半天。记住,const 不是 immutable。
不要滥用 readonly:在 TS 中,如果对象需要部分属性可变,部分属性只读,合理标记 readonly 能提高代码可维护性。但全量 readonly 可能导致灵活性不足。
注意 as const:TS 中有一个 as const 断言,可以将字面量类型固化。例如 const role = 'admin' as const;,这样 role 的类型就是 'admin' 而不是 string。这在处理枚举和配置时非常有用,也是近年来的高频考点。真实案例分享:
在某大厂面试中,候选人答出了 const 和 readonly 的基本区别,但被追问“如果我要保证一个配置对象在运行时绝对不可变,该怎么做?”候选人卡壳了。我给出的建议是:先用 const 声明变量,再用 Object.freeze 冻结对象,如果是嵌套结构,写一个 deepFreeze 工具函数。这个回答不仅展示了语言知识,还展示了工程化解决问题的能力,最终拿到了 Offer。
官方文档细节:
根据 MDN Web Docs(官方文档)的描述,const 声明一个只读变量引用,该引用必须在其声明时初始化。对于对象,const 防止的是重新分配,而不是修改属性。TypeScript 官方文档则明确指出,readonly 属性只能在对象初始化时赋值,后续修改会导致编译错误,但运行时不保证不可变。这些细节在面试中提出来,会显得你非常严谨和靠谱。
结尾互动:
你在项目里踩过这个坑吗?比如用了 const 结果属性还是被改了,或者用了 readonly 结果在 JS 运行时居然能修改?评论区聊聊,看看有多少人中过招。如果是你,你会怎么在团队中规范使用 const 和 readonly?
