前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库TypeScript 数组协变 readonly 写入安全
TyTypeScript泛型设计

TypeScript 数组为什么允许协变赋值,readonly 怎样减少不安全写入?

同一个数组出现两个不同的静态视图时,较宽视图的写入可能破坏较窄视图的假设。

前端进阶之旅 · 一题精讲更新于 2026.09.06
TypeScript#泛型设计#类型系统
先看核心答案读代码示例
理解线索

区分读取方便与写入风险

  1. 协变读取具体元素也能按基础能力被观察
  2. 宽视图写入可能插入原具体契约没有允许的元素
  3. 只读边界不给观察者数组增删与替换元素的能力

const 数组变量只限制重新绑定,不能替代 readonly 数组接口。

核心回答

先记住这个答案

TypeScript 通常允许把较具体元素数组赋给较宽元素数组,例如带点击坐标的事件数组可以交给通用事件数组变量。这种协变兼容方便读取,但可变数组存在漏洞:通过较宽引用写入不带坐标的事件后,原数组仍被静态视为点击事件数组,后续读取可能失败。strict 并不会完全消除这类取舍。只读取的 API 应接收 readonly T[],限制自身通过该接口写入;但 readonly 主要是静态且浅层的约束,不会冻结对象或阻止其他可写别名改变原数组。

  • 较宽可变数组视图可能写入较窄数组不接受的值
  • 严格模式仍保留部分容器兼容取舍
  • 只读参数限制当前使用者写入而非冻结所有别名

同一个引用如何形成不一致假设

点击事件包含通用事件的字段,所以从读取角度看,把点击数组当成通用数组没有困难。问题出现在写入:通用数组允许加入只有基础字段的事件,这个操作会修改同一个底层数组,而最初变量的静态类型没有因此同步变宽。

示例故意展示一次被允许的较宽写入,再提供只读观察函数。写入后数组实际包含没有坐标的元素,说明不能从最初变量类型推断每个运行值都可靠。示例没有继续调用缺失坐标的方法,而是把风险停留在可观察的数据变化上。

宽引用写入与只读接口的区别TypeScript
type EventBase = { kind: string };
type ClickEvent = EventBase & { x: number };
const clicks: ClickEvent[] = [{ kind: 'click', x: 10 }];
const events: EventBase[] = clicks;
events.push({ kind: 'focus' });
function count(items: readonly EventBase[]): number {
  // @ts-expect-error 只读观察接口没有 push
  items.push({ kind: 'blur' });
  return items.length;
}
const size = count(clicks);

events 与 clicks 是同一数组,宽引用写入后长度为二,第二项没有 x。只读参数阻止观察函数直接 push,但不会撤销已经发生的别名写入或替所有调用者冻结数据。

为什么 strict 没有自动解决一切

TypeScript 的结构兼容与方法参数规则保留了一些方便既有 JavaScript 使用的取舍,可变数组也受到这些规则影响。把数组描述为严格模式下完全不变,或声称编译通过就没有写入漏洞,都不符合实际行为。应以当前编译器最小例子验证。

如果 API 只遍历、查找或计算统计值,readonly 输入更准确地表达它不需要写入能力,还能接受只读元组。如果 API 要排序或追加元素,可以先创建自己的可变副本,或明确要求调用者交出可变容器,并说明是否会修改原引用。

浅只读与共享元素仍有边界

readonly T[] 限制数组层面的修改,不会自动把每个 T 的字段变成只读。即使不能替换某个元素,元素对象本身仍可能被修改。需要保护深层结构时,应按实际数据模型设计只读视图、复制或不可变更新,不能只在最外层加修饰符。

其他持有可变引用的代码也仍能改变原数组。跨异步阶段依赖稳定集合时,可能需要快照,而不是只把参数类型改成 readonly。测试应覆盖同一数组的多个别名和元素对象共享,确认观察函数的假设在真实生命周期内成立。

回答前,多想一步

容易答错的地方

开启 strict 后可变数组就一定不变
严格检查并没有消除所有历史兼容取舍,较具体数组仍可能通过宽引用被写入不相容元素;需要理解并限制实际写入路径。
readonly 数组意味着所有元素深度冻结
它主要限制这个静态数组接口的修改能力,元素对象和其他可写别名仍可能变化;深层不可变需要另外设计和实现。
试着用自己的话回答

面试官还会怎么问?

普通数组可以传给 readonly 参数吗?

通常可以,观察者不需要写入能力,所以可变数组能够满足较小的只读使用契约;这也让只读参数更适合纯计算函数。

复制数组就完全隔离了吗?

浅复制能隔离数组增删与元素替换,但其中的对象元素仍然共享;若会修改元素字段,还要决定是否复制更深层或采用不可变更新。

为什么 const clicks 仍然可以 push?

const 限制变量不能重新指向另一个数组,数组内容仍可变;限制当前接口的内容写入需要 readonly 类型或相应运行时措施。

从一道题,走向一组知识

把知识连起来

泛型设计

TypeScript strictFunctionTypes 为什么检查函数属性,却给方法参数保留双向兼容?

方法参数的兼容例外解释部分容器安全取舍

泛型设计

TypeScript 的 const 类型参数怎么写,可变与 readonly 约束怎样影响推断?

字面量名单与只读数组约束需要共同设计

参考资料

  • TypeScript:ReadonlyArray
  • TypeScript:Strict Function Types

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 同一个引用如何形成不一致假设
  3. 为什么 strict 没有自动解决一切
  4. 浅只读与共享元素仍有边界
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑