Damnatiox
DOCUMENT / published

数组、元组、对象、索引签名与只读边界

数组、元组、对象、索引签名与只读边界 TypeScript 对象类型描述“值至少有哪些成员”。这是一套结构化模型,不会在运行时创建 schema。 1. 数组与只读数组 readonly number[] 禁止通过该引用修改数组,但原始可变数组若还有别名,仍可被修改: 因此 readonly 是能力限制,不是深冻结或拷贝。 2. 元组 元组适合位置语义稳定、元素少的返回值。字段多或含义可能演进时优先对象,避免调用方记忆下标。 可选与剩余

TypeScript语言基础 2026/8/266 分钟阅读
# 语言基础# TypeScript# TypeScript语言基础

数组、元组、对象、索引签名与只读边界

TypeScript 对象类型描述“值至少有哪些成员”。这是一套结构化模型,不会在运行时创建 schema。

1. 数组与只读数组

TypeScript
const scores: number[] = [92, 87, 100] const names: Array<string> = ["Ada", "Linus"] const readonlyScores: readonly number[] = [92, 87] scores.push(95) // readonlyScores.push(95) // 编译错误

readonly number[] 禁止通过该引用修改数组,但原始可变数组若还有别名,仍可被修改:

TypeScript
const mutable = [1, 2] const view: readonly number[] = mutable mutable.push(3) console.log(view.length) // 3

因此 readonly 是能力限制,不是深冻结或拷贝。

2. 元组

TypeScript
type Point = readonly [x: number, y: number] type Result<T, E> = readonly [ok: true, value: T] | readonly [ok: false, error: E] const point: Point = [10, 20] const result: Result<number, string> = [true, 42]

元组适合位置语义稳定、元素少的返回值。字段多或含义可能演进时优先对象,避免调用方记忆下标。

可选与剩余元素

TypeScript
type Command = [program: string, cwd?: string, ...args: string[]] const command: Command = ["git", "/repo", "status", "--short"]

3. 对象属性修饰符

TypeScript
interface UserProfile { readonly id: string displayName: string avatarUrl?: URL } function rename(profile: UserProfile, name: string): UserProfile { return { ...profile, displayName: name } }
  • readonly 阻止通过该类型写入属性。
  • ? 表示属性可能缺少;在 exactOptionalPropertyTypes 下,“缺少”与“显式赋 undefined”不再自动等同。
TypeScript
interface ExactOptions { color?: "light" | "dark" } const a: ExactOptions = {} // const b: ExactOptions = { color: undefined } // 开启 exactOptionalPropertyTypes 后报错

若确实允许显式 undefined,写为 color?: "light" | "dark" | undefined

4. 索引签名与安全索引

TypeScript
type Inventory = { [sku: string]: number } const inventory: Inventory = { book: 3 } const count = inventory["missing"] // 开启 noUncheckedIndexedAccess 后为 number | undefined

动态键集合常可用 Map 或限定键的 Record 表达得更准确:

TypeScript
type Locale = "zh-CN" | "zh-TW" | "en" const labels: Record<Locale, string> = { "zh-CN": "语言", "zh-TW": "語言", en: "Language", }

新增联合成员时,Record 会要求补全对应键。

5. 多余属性检查与结构兼容

TypeScript
type Point2D = { x: number; y: number } function lengthOf(point: Point2D): number { return Math.hypot(point.x, point.y) } const point3D = { x: 3, y: 4, z: 5 } lengthOf(point3D) // 结构兼容 // lengthOf({ x: 3, y: 4, z: 5 }) // 新鲜对象字面量触发多余属性检查

这项差异用来捕捉对象字面量拼写错误;不要用中间变量“绕过”真实建模问题。

6. 深只读与运行时不可变

TypeScript
type DeepReadonly<T> = T extends (...args: never[]) => unknown ? T : T extends readonly unknown[] ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T

上述是静态递归类型,仍不会冻结对象。Object.freeze 是浅层运行时操作;深冻结需要递归实现,并注意 MapSet、日期、循环引用与性能。

7. 对象建模清单

  • 是否真的允许缺少属性,还是值允许为空?
  • 键集合是开放字符串,还是封闭联合?
  • 调用方需要写权限吗?
  • 是否应该返回新对象而非修改参数?
  • 外部对象是否已经运行时校验?
  • Record<string, T> 的缺失键是否通过 noUncheckedIndexedAccess 体现?

参考:Object TypesTSConfig: exactOptionalPropertyTypesTSConfig: noUncheckedIndexedAccess