想象你是写字楼的新任总务,每天有成百上千个“数据”要进出不同楼层。但楼里没有门禁,谁都能乱窜:有人把“年龄”写成了“名字”,有人把“价格”当“数量”用,出了事故才有人发现放跑了错误的“人”。 这时保安队长提出方案:给每个进入大楼的人办一张通行证,标明身份——这就是 TypeScript 做的事。它给 JavaScript 的数据贴上“类型标签”,让代码在运行之前,就能通过门禁检查揪出那些低级错误。你只需要学会辨认不同类型的“通行卡”,再学会处理“员工卡或访客卡都能进”的联合情况,就能让整栋大楼井然有序。

岗位关联

学懂 TypeScript 基本类型与联合类型,是你从“会写 JS”走向“能驾驭大型前端工程”的第一道门禁,能让协作更安全、重构更自信、Bug 更少。

目录
  1. 可视化演示
  2. 类型系统入门
  3. 基本类型解剖
  4. 联合类型
  5. 类型推断
  6. 类型守卫
  7. 工程配置与迁移
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示TypeScript类型核心机制:基本类型声明、联合类型分支与类型守卫收窄,可视化编译期类型检查如何提升代码安全性。

本节配有交互动效(进入视口后自动加载,约 3KB)。

基本类型声明 string/number/boolean等基本类型在编译期检查 类型错误在编译阶段而非运行时暴露 let x: type = value

类型系统入门

大楼的保安队长先给你看了一段监控:因为没有门禁,一位“价格”溜进了“姓名”通道,结果收银台把客人的名字打印成了数字。JavaScript 就像这座不设防的大楼——变量随时可以变成任何类型,灵活但危险。

TypeScript 的理念很简单:“数据进门之前,先验明正身。”它会静态地检查每一个变量、函数参数和返回值,在代码运行之前就把类型矛盾暴露出来。这不是运行时监控,而是编译期门禁:只要类型不合规,编译就直接亮红灯。

类型系统本质上是在描述“哪些值属于哪些集合”。这份集合关系可用公式表达:

类型系统不是限制你,而是像门禁一样保护你:它可以告诉你“这扇门只能让 number 进去”,省去运行时飞来飞去的排查。给整栋楼装上这个门禁系统,正是 TS 存在的意义。

T=S∪N∪B∪O∪⋯\displaystyle \mathcal{T} = \mathcal{S} \cup \mathcal{N} \cup \mathcal{B} \cup \mathcal{O} \cup \cdots
这份公式说明:整个类型宇宙由字符串集合、数值集合、布尔集合、对象集合等组成,TS 的类型就是这些集合的划分。
  1. 所有类型集合\mathcal{T} 代表 TypeScript 中一切可能类型的总和,相当于“整栋大楼的通行证总览”。
    T\mathcal{T}
  2. 字符串集合\mathcal{S} 是所有字符串的集合,比如 "hello"、"123" 都属于它。
    S={所有字符串}\mathcal{S} = \{\text{所有字符串}\}
  3. 并集运算\cup 表示集合的并集,意思是“这些类型共同组成了类型宇宙”。
    S∪N\mathcal{S} \cup \mathcal{N}
  4. 省略更多\cdots 表示还有布尔值、对象、数组等更多类型集合没有一一列出。
    ⋯\cdots

基本类型解剖

门禁系统发了好几款通行卡,颜色不同、权限不同。最基础的是四张“原始卡”:蓝色员工卡 string 存文本,红色访客卡 number 存数字,绿色临时卡 boolean 存真/假,还有很少单独占位的 null 和 undefined 两张“无权限卡”。

除了原始卡,还有“钱包型”的 object 可以装多个身份信息,array 是一叠同类卡片,tuple 是“先姓名后年龄”的固定卡槽。更特殊的是两张“万能卡”:any 表示放弃检查,谁都能刷;unknown 则是一张“待验证卡”,必须经过检查才能确定身份。

这些类型可以看作一个卡片集合:

理解了这些卡片,你写 let age: number = 30 时,就像给变量发了一张红色访客卡——别人再也不能往里面塞 "三十" 这个蓝色字符串了。保安看到卡色,就知道该放你进哪扇门。

Card={string,number,boolean,null,undefined,object,array,any,unknown}\displaystyle \text{Card} = \{ \text{string}, \text{number}, \text{boolean}, \text{null}, \text{undefined}, \text{object}, \text{array}, \text{any}, \text{unknown} \}
一张通行卡只能对应一种身份,选择正确的卡片类型,是安全使用 TS 的第一步。
  1. 卡片分类函数\text{Card} 表示“把值分类到哪种卡”,对应 TS 里的类型注解。
    Card:value→type\text{Card}: \text{value} \to \text{type}
  2. 原始卡成员\text{string}、\text{number}、\text{boolean} 是三种最常见的原始类型。
    {string,number,boolean}\{\text{string}, \text{number}, \text{boolean}\}
  3. 特殊卡成员\text{any} 是“开挂卡”,\text{unknown} 是“待定卡”,两者作用完全不同。
    {any,unknown}\{\text{any}, \text{unknown}\}

联合类型

现实里,一扇门往往不认单一卡种:“访客走 1 号闸机,员工走 2 号闸机,但临时工没有专属卡怎么办?”门禁系统想了个办法:发一张“跨区双功能卡”,上面写着 string | number,表示只要持有其中任意一种身份就能通行。

联合类型用竖线 | 把多个类型连接起来,让变量拥有“或”的关系。它特别适合表达“这个字段可能是数字 ID,也可能是字符串 ID”之类的真实场景。你还可以联合 null 模拟可空值,或者联合固定的字面量字符串,实现类似枚举的选项。

一个联合类型在数学上就是多个集合的并集:

联合类型给你提供了弹性,但门禁依然不会让你随便进——数据进入函数后,保安还得分清它到底是哪种身份,这就轮到类型守卫登场了。

string∪number=string∣number\displaystyle \text{string} \cup \text{number} = \text{string} \mid \text{number}
| 符号读作“或者”,它把类型选择权交给了数据的实际值,而不是预先钉死。
  1. 左边集合\text{string} \cup \text{number} 表示“字符串或数字的并集”,值可以是其中任意一个集合的成员。
    string∪number\text{string} \cup \text{number}
  2. 结果写法\text{string} \mid \text{number} 是 TypeScript 中联合类型的实际语法。
    string∣number\text{string} \mid \text{number}
  3. 等式含义两者等价,说明 TS 的类型操作符 | 本质上就是集合的并集。
    string∪number=string∣number\text{string} \cup \text{number} = \text{string} \mid \text{number}

类型推断

老保安有一种本领:看到你胸口的工牌颜色,不用你开口,他就能判断你的身份。TS 也这样——你写 let name = "Alice",它自动推断出 name 的类型是 string,根本不需要你画蛇添足地写 : string。

推断规则很有讲究:let 声明的变量会推断为宽泛的基础类型,比如 let x = 1 推断为 number;而 const 声明因为不可变,会推断为更精确的字面量类型,比如 const c = "a" 推断为 "a"。函数的返回值、泛型参数,也能根据表达式自动推断。

推断的过程简洁而强大:

类型推断让门禁系统变得贴心:大部分常见情况不用你手动贴标签,保安看初值就知道你是谁。但要注意,当 TS 推不出准确类型时,它会“偷懒”成 any——这时候你最好主动补上类型,别让门禁形同虚设。

let x=1  ⟹  x:number\displaystyle \text{let } x = 1 \;\Longrightarrow\; x : \text{number}
从初始化表达式到类型结论,TS 用一条隐式推理链完成了类型标注。
  1. 变量声明\text{let } x = 1 是 JavaScript 的普通写法,TS 会读取这个初值。
    let x=1\text{let } x = 1
  2. 推理箭头\Longrightarrow 表示“由此推导出结论”。
    ⟹\Longrightarrow
  3. 结论标注x : \text{number} 表示 x 被自动判定为 number 类型。
    x:numberx : \text{number}

类型守卫

拿到联合类型的数据后,保安不能凭一张“双功能卡”就放行。他会先问:“你到底是员工还是访客?” 然后根据回答,把你引导到对应的闸机。这个“根据类型选择分支”的过程,就叫类型守卫。

TS 最常用的守卫工具是 typeof,它可以精确判断 string、number、boolean 等原始类型。instanceof 适合判断类实例,in 适合判断对象是否拥有某个属性。每做完一次判断,TS 就会在分支内“收窄”变量的类型,让它从 string | number 变成单一的 string 或 number。

收窄的逻辑就像条件判断的推理:

有了守卫,联合类型不再是“薛定谔的身份”,而是“过闸机后明确身份”。记住:收窄越细致,你的代码就越不容易把字符串当数字去乘、把数字当字符串去拼接。

typeof v=’string’  ⟹  v:string\displaystyle \text{typeof } v = \text{'string'} \;\Longrightarrow\; v : \text{string}
当 typeof 检查命中时,TS 会在当前分支内把 v 的类型收缩为 string,这就是类型收窄。
  1. 守卫条件\text{typeof } v = \text{'string'} 是运行时判断,检查变量 v 是否为字符串。
    typeof v=’string’\text{typeof } v = \text{'string'}
  2. 推理结果\Longrightarrow 表示满足条件后推导出的新结论。
    ⟹\Longrightarrow
  3. 收窄类型v : \text{string} 表明在这个分支里 v 被安全地当作 string 使用。
    v:stringv : \text{string}

工程配置与迁移

门禁系统再好,也得有制度支撑。tsconfig.json 就是整栋楼的“安保制度文件”,它决定了哪些门禁规则必须开启。其中最要紧的是 strict 模式——开启后,TS 会拒绝所有隐式的 any,并把 null 和 undefined 都纳入检查,让安全级别瞬间拉满。

对于已经运行多年的老 JS 项目,你不可能一夜之间给所有人都补办卡片。聪明做法是“渐进式迁移”:先开启 allowJs 让 TS 项目能混编 JS 文件,再用 checkJs 逐步启用检查,最后对最关键的模块优先收编。就像先给重要楼层装门禁,再慢慢推广到全楼。

严格模式可以理解为多条规则的汇总:

别把 strict 当成负担,它是门禁的“最高权限设置”。老项目迁移也一样,不要急着推倒重建——先把门禁装上,再逐步收紧,才能让团队在风平浪静中完成过渡。

strict=noImplicitAny∧strictNullChecks∧⋯\displaystyle \text{strict} = \text{noImplicitAny} \land \text{strictNullChecks} \land \cdots
严格模式不是一个单独开关,而是多个安全规则的集合,缺一不可。
  1. 严格模式\text{strict} 是 tsconfig 中的总开关,开启表示采用最高安全配置。
    strict\text{strict}
  2. 逻辑与\land 表示“并且”,说明这些规则必须同时满足。
    ∧\land
  3. 关键子规则\text{noImplicitAny} 禁止隐式 any,\text{strictNullChecks} 强制处理空值。
    noImplicitAny∧strictNullChecks\text{noImplicitAny} \land \text{strictNullChecks}

AI 应用场景

前端团队代码巡检

阶段:数据标准化与代码质量保障

解决的问题:在多模块协作中,接口返回的数据常被随意使用,导致取值错误。TS 的类型标注相当于给每个接口数据提前盖好“通行证章”,AI 分析的字段类型与真实数据一致,减少线上事故。

具体案例:某团队在接入用户画像 API 后,用 type User = { name: string; age?: number; tags: string[] } 定义数据结构。前端在消费数据时,若误读 age 为字符串,编译期就会报错,而不是等运行后页面白屏。

旧项目渐进式迁移

阶段:技术债清理与系统重构

解决的问题:存量 JS 代码直接迁移 TS 风险极高,容易引发大面积回归。采用 allowJs + checkJs 的分步策略,像先给老楼装上部分门禁再逐步扩容,让迁移过程平滑可控。

具体案例:一个运营后台系统有 200 多个 JS 文件,团队先开启 allowJs 混编,再按业务模块逐个增加类型声明,最后开启 strict。三个月内完成迁移,线上故障率下降 40%。

数据模型的联合处理

阶段:多源数据接入与规范化

解决的问题:多个数据源返回的 ID 可能是字符串也可能是数字,用联合类型 string | number 统一建模,配合 typeof 守卫分支处理,避免不必要的字符串转换。

具体案例:在 IoT 平台采集设备数据时,deviceId 有的来源是 "sensor-01",有的是 1001。统一收窄后分别走不同匹配逻辑,系统稳定性显著提升。

Python 演示

python16 行
def simulate_type_narrowing(value):    if isinstance(value, str):        return "string 通道: " + value.upper()    if isinstance(value, int):        return "number 通道: " + str(value * 2)    if value is None:        return "null 通道: 检查失败"    return "unknown: 需要更多守卫" def main():    samples = [42, "hello", None, 3.14]    for s in samples:        print(simulate_type_narrowing(s)) if __name__ == "__main__":    main()
目的:用 Python 模拟 TS 中 typeof 类型守卫与收窄的过程,展示不同数据类型进入分支后如何被分类处理。
  1. def simulate_type_narrowing(value):模拟一个接受联合类型参数的处理函数,对应 TS 的 function process(value: string | number | null)。
  2. if isinstance(value, str):等价于 if (typeof value === "string"),判断数据是否为字符串。
  3. return "string 通道: " + value.upper()在 str 分支内安全调用字符串方法,对应 TS 收窄后的类型安全操作。
  4. if isinstance(value, int):对应 typeof value === "number" 的守卫条件。
  5. return "unknown: 需要更多守卫"对应 TS 中 unknown 类型:未经过守卫确认前,不能贸然使用。

常见误区

误区我以为 TypeScript 会在代码运行时帮我拦截类型错误,像代码报错后还能提示“这里类型不对”。

正解TS 的类型检查发生在编译期,也就是代码运行之前。一旦编译通过,生成的 JavaScript 中就没有类型信息了。它像大楼门口的安检,进门之后保安不会再跟着你;运行时错误依然需要逻辑来兜底。

为何常见:因为我们在编辑器里看到了红色波浪线,误以为是“运行时保护”,实际那是编译器的静态提示。

误区遇到不确定的类型,我直接写成 `any`,省得思考,反正也能编译通过。

正解any 等于关闭了这道数据的门禁,任何人都能混进来,类型系统瞬间失效。如果你整天用 any,那 TS 的价值基本归零。应该优先用 unknown 配合类型守卫,或者用联合类型精准表达可能情况。

为何常见:any 写起来最快、报错最少,容易让人产生“类型检查很麻烦”的错觉。

误区既然 TS 能自动推断所有类型,那我干脆不写了,让编译器猜。

正解类型推断确实能处理大量初值、返回值场景,但函数参数、复杂接口、跨模块边界等位置,TS 无法获得足够上下文,往往会退化成 any。门禁系统也需要你手动设置关键卡口,否则它不知道访客该去哪层。

为何常见:刚上手时发现 let x = 1 自动就是 number,误以为推断万能。

误区声明了 `let x: string | number` 之后,我以为在代码里可以直接把 x 当字符串用,毕竟我已经说了它可能是字符串。

正解TS 不会自动帮你在分支里确定类型,必须通过 typeof、instanceof、in 等守卫主动收窄,才能安全使用具体类型的属性或方法。否则,TS 会在你调用 .toUpperCase() 时提示 number 类型不存在该方法。

为何常见:人看到“联合”会理解成“都包含”,而 TS 更严格:未收窄前,能用的方法必须是所有成员共有的。

误区既然要用 TS,就应该把所有 .js 文件一次性改成 .ts,否则感觉不彻底。

正解大型老项目强行全量迁移会导致巨大的回归风险和团队阻力。推荐渐进式策略:先开启 allowJs 混编、再开启 checkJs、然后按模块逐个改为 .ts 并逐步收紧 strict。门禁不必一天装好,慢慢扩大覆盖范围更稳妥。

为何常见:看到官方文档的“快速迁移”例子,误以为所有项目都能在短时间内完成转换。

面试题

Q1. TypeScript 相比 JavaScript 最大的优势是什么?

单选题 · easy

  • A.运行速度更快
  • B.编译期类型检查← 正确答案
  • C.无需编译直接运行
  • D.浏览器原生支持

答案:B。TS 的核心价值是在编译期做静态类型检查,让类型错误在运行前被揪出来。运行速度、原生支持与 JS 并无区别,“无需编译”恰恰相反,TS 必须编译成 JS 才能运行。

Q2. 下列哪一项是 TypeScript 中的原始类型?

单选题 · easy

  • A.object
  • B.string← 正确答案
  • C.array
  • D.function

答案:B。string、number、boolean 等都是原始类型,而 object、array、function 都属于对象类型。题目问“原始类型”,所以选 string。

Q3. let x: string | number 表示什么含义?

单选题 · mid

  • A.x 同时是 string 和 number
  • B.x 可以是两者中的任意一个← 正确答案
  • C.x 会自动在两种类型间切换
  • D.x 只能被赋值为 any

答案:B。联合类型用 | 表示“或”,x 在赋值时只能是 string 或 number 之一,而不是同时拥有两个身份。类型也不会自动切换,更不要求 any。

Q4. 关于 TypeScript 类型推断,下列说法正确的有哪些?

多选题 · mid

  • A.let n = 1 推断为 number← 正确答案
  • B.const c = "a" 推断为字面量类型← 正确答案
  • C.函数返回值可以根据逻辑推断← 正确答案
  • D.any 总是能被自动推断为具体类型

答案:ABC。A、B、C 都是常见的推断行为。D 错误:any 本身代表放弃检查,不会自动变成更具体的类型,反而会“污染”后续推断。

Q5. 下列哪个类型用 typeof 做守卫时最安全可靠?

单选题 · mid

  • A.string← 正确答案
  • B.object
  • C.array
  • D.null

答案:A。typeof x === "string" 能准确判断所有字符串。而 typeof [] === "object",无法区分数组和普通对象;typeof null === "object" 更是经典坑。所以 string 最可靠。

Q6. 哪些运算符可以在 TypeScript 中用来实现类型收窄?

多选题 · hard

  • A.typeof 判断原始类型← 正确答案
  • B.in 判断对象属性← 正确答案
  • C.instanceof 判断类实例← 正确答案
  • D.数组索引直接收窄

答案:ABC。typeof、in、instanceof 都是 TS 官方认可的类型守卫写法。数组索引本身不是类型守卫,无法让 TS 自动收窄,必须配合守卫表达式使用。

Q7. tsconfig.json 中开启“严格模式”的关键选项是?

单选题 · easy

  • A.strict← 正确答案
  • B.outDir
  • C.moduleFormat
  • D.syntaxLevel

答案:A。strict 是总开关,开启后同时启用 noImplicitAny、strictNullChecks 等严格规则。outDir 是输出目录,moduleFormat 并非标准选项,所以选 strict。

Q8. 关于 any 和 unknown,以下说法正确的是?

多选题 · hard

  • A.any 会关闭该变量的类型检查← 正确答案
  • B.unknown 必须先收窄才能赋值给具体类型← 正确答案
  • C.any 可以把值赋给任何类型的变量← 正确答案
  • D.unknown 可以像 any 一样直接调用任意方法

答案:ABC。any 完全跳过检查,可赋给任意类型;unknown 是“待定身份”,必须通过守卫收窄后才能安全使用。D 错误,unknown 不能直接调用方法,否则违背了它“待验证”的本意。

Q9. 将老 JS 项目迁移到 TS 时,最推荐的策略是?

单选题 · mid

  • A.一次性把全部文件改为 .ts
  • B.开启 allowJs 再逐步收紧检查← 正确答案
  • C.放弃类型检查只保留后缀
  • D.所有变量都改成 any 再改回来

答案:B。渐进式迁移风险最小:先 allowJs 混编,再 checkJs,再按模块收编。一步到位容易引发大规模回归;用 any 过渡虽然常见,但必须配合逐步收窄,不能作为最终策略。

Q10. 类型守卫让联合类型在分支内变成具体类型的过程叫什么?

单选题 · mid

  • A.类型收窄← 正确答案
  • B.类型扩张
  • C.类型合并
  • D.类型覆盖

答案:A。从 string | number 收窄到 string,如同机场安检把“员工卡或访客卡”最终确认为员工卡,这个精确化的过程叫类型收窄。其余三个术语都不是 TS 的标准概念。