老李开了一家快递仓库,十几年都是“装车就走”的粗放模式。直到有一次,货车开到客户楼下,卸下来才发现本该送冰箱的车里装的是洗衣机——退货、赔钱、赔信誉。问题出在哪?出在“发出去之前没人检查”。老李痛下决心,在每条出库流水线上加装一个“类型检查站”:每件货物装箱时必须先贴好标签,纸箱装的是文本,电器装的是数字,开关装的是真假;检查站的师傅把标签和实物一一核对,对的放行,错的当场拦下。 这个故事讲的就是 TypeScript。它并不是一门全新的语言,而是在 JavaScript 这座“老仓库”上加装了一套类型检查体系。JavaScript 的变量要等程序真正跑起来才知道是什么货,错货坏货全都在客户收货时才暴露;TypeScript 则在代码变成 JavaScript 之前,就像检查站的师傅一样把货物逐件核验,把最常见的一类错误拦在“发货”之前。接下来,我们就用这座仓库的升级故事,认识标签(基础类型)、二选一通道(联合类型)、组合规格(交叉类型)、给规格起名(类型别名)、签规格合同(interface)、老师傅的直觉(类型推导),以及如何让老仓库一层层装上新检查站(渐进式迁移)。

岗位关联

类型系统是前端工程化的地基:掌握它,你就能在代码提交前用编译器替代肉眼排查,把低级 bug 挡在门外——这是每一位 AI 应用前端开发者必备的工程素养。

目录
  1. 可视化演示
  2. 类型系统概览
  3. 基础类型
  4. 联合类型
  5. 交叉类型
  6. 类型别名
  7. 接口 interface
  8. 类型推导
  9. 渐进式迁移
  10. AI 应用场景
  11. Python 演示
  12. 常见误区
  13. 面试题

可视化演示

展示从JavaScript到TypeScript的渐进迁移路径,以及TS的六大类型核心概念:Basic(基本类型)、Union(联合)、Inter(交叉)、Type(别名)、Interf(接口)、Infer(推导)。

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

TypeScript类型系统 Basic(str/num/bool)、Union(A|B)、Inter(A&B)三大基础类型组合,Type(别名)、Interf(接口)、Infer(推导)三大高级特性 type T = A | B; interface I { x: number } 渐进式迁移:JS代码直接改.ts后缀即可,编译时(Compile)捕获错误替代运行时暴露

类型系统概览

老李吃了发错货的亏,回来后第一件事就是找老师傅商量。老师傅说:“以前仓库小,货在眼前,靠眼睛盯就够;现在每天上千件货,谁还记得住哪个箱子装的是什么?”JavaScript 项目也一样:变量类型完全自由,一个人写的时候没问题,团队协作、项目变大之后,age 到底是数字还是字符串,只有程序跑起来的瞬间才知道——这就是“动态类型”的隐患。

TypeScript 的解法,是给 JavaScript 加一套“静态类型检查”。所谓“类型”,就是“这个值是什么货”;静态,就是“发货前定下来并核验一遍”。于是有了 let age: number = 18 这样的写法:: number 就是给变量贴上“数字类”标签,检查站师傅在编译期逐一核对,标签与实物不符就直接拦下。

需要强调的是,这套检查并不改变程序的运行方式。TypeScript 编译器完成核验后,产出的仍然是纯 JavaScript,老仓库的货车、公路、送货流程一条不变。

装上了检查站,仓库还是那个仓库,货车路线、配送时效都没变,只是从此“错货”在出库门前就被拦下。理解了这个定位,后面所有类型知识,都只是检查站里一张张具体的“标签规则”。

TypeScript=JavaScript+Type System\displaystyle \text{TypeScript} = \text{JavaScript} + \text{Type System}
这个公式是 TS 的定位:在原有语言之上叠加一个“检查层”,而不是另起炉灶。
  1. JavaScript 部分这是你早已掌握的那门语言,语法、运行逻辑、生态全部原样保留,之前的代码一行都不用丢弃。
    JavaScript\text{JavaScript}
  2. Type System 部分额外叠加的静态类型规则,相当于仓库里新增的“检查站”,专门核验发货前的标签与实物。
    Type System\text{Type System}
  3. 加号组合加号表示“组合”而非“替换”;检查只在编译期生效,最终交付的仍然是可以直接运行的 JS。
    编译期检查→纯 JavaScript\text{编译期检查} \rightarrow \text{纯 JavaScript}

基础类型

检查站上班的第一天,师傅们先要认识“货品类别”——也就是贴纸。老李的仓库一共有几种最常用的标签:文本类的 string,电器类的 number,开关类的 boolean,还有表示“空包裹”的 null 和 undefined,以及装着杂七杂八东西的 object 和 array。这就是 TypeScript 的基础类型。

写法上非常直观,声明变量时在冒号后写上类型即可:

数组和对象也有自己的形状:number[] 表示“一排数字”;[number, string] 这种元组表示“第一个位置必须装数字、第二个位置必须装文本”的固定顺序货包。对象则用花括号描述“箱子里必须有哪些隔层”。

基础标签就是检查站的地基。有了这些贴纸,师傅们才能谈“二选一”和“全都要”——也就是后面要认识的联合类型与交叉类型。

T∈{string, number, boolean, null, undefined, object, array, ⋯ }\displaystyle \text{T} \in \{ \text{string},\ \text{number},\ \text{boolean},\ \text{null},\ \text{undefined},\ \text{object},\ \text{array},\ \cdots \}
任何一个变量的类型 T,都必须来自这个“基础标签库”。
  1. T ∈ 集合任何变量的类型 T,都必须属于这个“类型标签库”中的某一类,就像每件货物都要从标签册里选一张贴纸。
    T∈{⋯ }\text{T} \in \{ \cdots \}
  2. string / number / boolean三个最常用的原始类型:文本用 string,数字用 number,开关状态用 boolean;这是检查站认货的第一批专业贴纸。
    string, number, boolean\text{string},\ \text{number},\ \text{boolean}
  3. null / undefined表示“空包裹”的两种特殊标签,null 常表示刻意留空,undefined 表示还没装入任何东西。
    null, undefined\text{null},\ \text{undefined}
  4. object 与 array复合类型标签,对应“一箱混装货”和“按次序排列的一排货”,它们的内部结构还能继续细化约束。
    object, array\text{object},\ \text{array}
ts3 行
let name: string = "tom";let age: number = 18;let isOk: boolean = true;

联合类型

新仓库上线后,发现一张发货单可能是客户在手机上下的电子单,也可能是传真来的纸质单——检查站得认两种格式。这个“要么 A 要么 B”的通道,就是联合类型,写法是用竖线 |:

联合类型的核验规则是“二选一放行”:标签与实物匹配其中任何一类就算过关。但问题来了——既然可能是 A 也可能是 B,检查站想知道“这张单到底是哪种”,就得现场判断。在 TS 里这叫“类型收窄”:

实战中还常用“可辨识联合”:给每种货物加一个特殊字面量标签,比如 { kind: "order" } 与 { kind: "invoice" },检查站靠 kind 一眼分流;而 string | null 这种“空值兜底”则时刻提醒你:这个值可能是空包裹,必须处理。

联合类型就像仓库的“多规格通道”:一车货可以走 A 门或走 B 门,但门卫只有确认你走的是哪扇门,才放行该门专属的卸货设备。

T=A∪B⇒值属于 A 或 B 其中之一\displaystyle \text{T} = \text{A} \cup \text{B} \quad \Rightarrow \quad \text{值属于 A 或 B 其中之一}
联合类型读作“或”,不是“和”。它是一条通道拆成两条候选道,货物走任意一条都算过关。
  1. A 与 B 两个候选类联合类型把一条通道拆成两条候选通道,货物只要匹配其中任意一条就能通过检查站。
    A, B\text{A},\ \text{B}
  2. ∪ 并集符号并集读作“或”,表示二选一;它和交叉类型里的“且”(∩)方向完全相反,是最容易混淆的一对符号。
    A∪B\text{A} \cup \text{B}
  3. 使用限制未收窄前,TS 只允许调用两边都支持的通用操作;要调用某一边专属的方法,必须先 typeof 收窄。
    typeof x=... ⇒ 收窄\text{typeof}\ x = \text{...} \ \Rightarrow \ \text{收窄}
ts2 行
let id: string | number = "abc-123";id = 99; // 同样合法
ts3 行
if (typeof id === "string") {  id.toUpperCase(); // 现在确认是 string, 可以放心调用字符串方法}

交叉类型

有些高端客户对包装有硬性要求:“既要防水,又要防火”——两种规格必须同时满足,缺一样都拒收。这种“全都要”的组合规格,就是交叉类型,用 & 表示:

交叉类型会把两边的“属性条款”合并成一个更大的形状:防水层要有,防火层也要有,组合成一个“既防水又防火”的包装箱。检查站核验时,每一项要求都要核对。

它和联合类型是仓库里最容易被拿反的两张规则:A | B 是“二选一挑一个用”,A & B 是“合在一起全部都要”。还有一个极端情况:如果两个规格互相矛盾,比如 string & number——要一个“既是文本又是数字”的箱子,根本做不出来,结果就是空类型 never。

交叉类型就是检查站里的“组合规格单”:要求每一条都打勾,少一层防火材料,箱子就不合格,直接退回。

T=A∩B⇒对象同时拥有 A 与 B 的全部属性\displaystyle \text{T} = \text{A} \cap \text{B} \quad \Rightarrow \quad \text{对象同时拥有 A 与 B 的全部属性}
交叉类型读作“且”:规格叠加,缺一不可。
  1. ∩ 交集符号从集合论看是交集,但在类型系统里它表示“规格叠加”——两个规格的要求全部都满足。
    A∩B\text{A} \cap \text{B}
  2. 属性合并当 A 和 B 都是对象类型时,交叉结果会合并两边的属性,形成一个更大的对象形状。
    {a:A}∩{b:B}={a, b}\{ a: \text{A} \} \cap \{ b: \text{B} \} = \{ a,\ b \}
  3. 冲突变成 never若两边要求互相矛盾(如 string & number),任何值都无法同时满足,结果就是空类型 never。
    string∩number=never\text{string} \cap \text{number} = \text{never}
ts3 行
type A = { x: number };type B = { y: string };type C = A & B;        // C 的值必须同时有 x 和 y

类型别名

发货单的规格组合越来越多,每次都在货单上写“文本或数字,或由系统生成的编号”太啰嗦。老师傅出了个主意:给常用规格起个名字,以后货单上只写名字,检查站翻规格本就能核验。这就是类型别名,用 type 关键字:

要点是:类型别名不创造新类型,它只是“老李”和“李老板”的关系——同一个人的两种称呼。检查站看到 UserId 时,会翻到规格册对应页面,按 string | number 的实际内容去核验。

别名的灵活性很高:可以指代联合、交叉、元组、对象等任何类型组合,还支持泛型参数成为“可定制尺寸的通用包装箱”:type Wrap<T> = { value: T }; 其中 T 由使用时现场指定。

别名让检查站的标签册从“几十种基础贴纸”扩展出“上千种组合规格”。规格再复杂,只要名字起得清楚,仓库的规则就能一直保持可维护。

type UserId=string∪number\displaystyle \text{type}\ \text{UserId} = \text{string} \cup \text{number}
别名 = 给一长串类型表达式起一个方便记忆和复用的称呼。
  1. type 关键字它告诉编译器:“我在规格册上登记一个新名字,之后可以随时引用它”,这就是起别名的仪式。
    type\text{type}
  2. 别名 UserId别名本身不产生新类型,它只是一个“称呼”,检查站看到它时,会翻到规格册去核验右侧的真实内容。
    UserId\text{UserId}
  3. 等号右侧右侧可以是联合、交叉、元组或对象等任意类型组合,这是别名最灵活的地方。
    string∪number\text{string} \cup \text{number}
ts5 行
type UserId = string | number;type Point = { x: number; y: number }; let uid: UserId = 42;let p: Point = { x: 1, y: 2 };

接口 interface

老李发现,和供应商长期合作不能只靠口头约定,得签合同:每批货必须符合合同条款,条款写什么,到货就核验什么。TypeScript 的 interface 就是这份“对象形状合同”:

合同中冒号是“必备条款”:任何自称 User 的对象,必须有 name 且它是 string。age? 是“可选条款”:可以没有;如果有,则必须是 number。检查站收货时,只核对合同上列出的条款。

接口之间还能“继承”——在主合同后面补签补充协议:interface Admin extends User { permissions: string[] }。管理员用户除了普通用户的条款,还多一条权限数组。另一个特点是“声明合并”:同名的 interface 会自动拼成一份更大的合同,就像两份补充协议拼在一起生效。

至于和 type 的区别:描述对象形状时两者都能用,社区惯例是“对象优先用 interface,遇到联合、交叉、元组时用 type”。这个边界,我们到误区部分再展开。

接口就是检查站与供应商之间那本“合作规格书”。合同条款越清晰,到货核验越顺畅,双方扯皮越少——放在代码里,就是前后端联调时的心照不宣。

interface User={name:string;  age?:number}\displaystyle \text{interface}\ \text{User} = \{ \text{name}: \text{string};\ \ \text{age}?: \text{number} \}
接口是一份“对象规格合同”:条款写得越清楚,核验越顺利。
  1. interface 关键字它用来声明一份“对象形状合同”,规定了对象必须具备哪些属性、各属性是什么类型。
    interface\text{interface}
  2. 必备条款 namestring:冒号表示“必须有且类型要匹配”,任何自称 User 的对象,没有 name 或 name 不是 string,检查站直接拒收。
    name:string\text{name}: \text{string}
  3. 可选条款 age?问号表示“可以没有;如果有,则必须是 number”,这是接口对“弹性要求”的表达方式。
    age?:number\text{age}?: \text{number}
  4. extends 扩展接口可以继承另一个接口,在原有条款基础上追加新条款,就像在主合同后面补签补充协议。
    interface Admin extends User\text{interface}\ \text{Admin}\ \text{extends}\ \text{User}
ts4 行
interface User {  name: string;  age?: number;}

类型推导

检查站的老师傅干了二十年,有个本事:瞟一眼箱子外形,就知道里面大概是什么货——根本不用等贴标签。TypeScript 也有这种“直觉”,叫类型推导:

推导的规则很朴素:从“初始值”反推变量类型,从“返回值表达式”反推函数返回类型,甚至能借助“上下文”判断——比如事件回调里的事件对象,TS 会根据你绑定的元素自动补全类型。这解释了为什么很多 TS 代码看起来“没写类型,但处处有类型”。

但老师的直觉也有边界:函数参数没有初始值,推导无从下手,只能靠显式标注;复杂嵌套的对象结构,推导结果可能不够精确,也需要标注。最佳实践是“能推导的少写,边界处写清楚”——把类型注解留给函数参数、返回值这类“检查站看不到线索”的位置。

类型推导不是“从此不用写类型”,而是“减少你不得不写的类型”。老师傅认得出的货不必贴标签,但碰上没见过的新式货物,还是要老老实实把规则写下来。

let x=42⇒x: number\displaystyle \text{let}\ x = 42 \quad \Rightarrow \quad x:\ \text{number}
推导 = 根据初始值的类型,自动得出变量的类型,省去手写标签。
  1. let x = 42 初始值变量声明时带着初始值,这个值就是检查站判断类型的“现场线索”。
    let x=42\text{let}\ x = 42
  2. ⇒ 推导箭头箭头表示编译器沿着值反向推断类型——看见数字 42,就断定 x 是 number,不需要手动标注。
    ⇒\Rightarrow
  3. xnumber 结论:推导出的类型会写进编译器的“类型环境”,之后任何给 x 赋 string 的行为都会在编译期被拦截。
    x: numberx:\ \text{number}
ts2 行
let age = 18;        // age 被推导为 numberlet name = "tom";    // name 被推导为 string

渐进式迁移

老李的仓库不可能一夜之间全部翻新。他的做法是:先挑一个新分拣区试点,装上检查站跑顺,再逐步铺到老分区,最后把整个仓库改造完成。JavaScript 迁移到 TypeScript 也是同样的节奏。

第一步,开通 allowJs,允许 .js 老文件和 .ts 新文件混在一个项目里,新代码开始用 TS 写,老代码原地不动。第二步,开启 checkJs,让编译器开始对 .js 文件也做检查,老文件的隐患逐渐浮出水面。第三步是 strict:从关闭的“宽松检查”逐步收紧到“全面严格”,让团队先适应再下猛药。第四步,给暂时没时间改的老代码标上 any——一张“临时通行证”,先放行再回头补标签,最终的目标是把 any 一个个换回精确类型。

迁移的优先级也有讲究:先给公共数据模型、工具函数补类型,收益最高;业务代码可以缓一缓。TypeScript 编译器会自己列出一份“哪里还缺类型”的检查清单,按着清单推进,就像按着检查站的拦截记录逐项整改。

老仓库改造完成那天,每一件出库货物都贴上了标签,每一张发货单都经过检查站。发货前的错误少了,投诉电话也少了——TypeScript 给工程带来的,正是这种“把问题拦在发货前”的踏实感。

strict: booleanfalse⇒true\displaystyle \text{strict}:\ \text{boolean} \quad \text{false} \Rightarrow \text{true}
strict 是 tsconfig 里的总开关:从宽松到严格,是渐进迁移的主轴线。
  1. strict 配置项这是 tsconfig.json 里的总开关,控制整个项目的类型检查严格程度,相当于决定检查站的工作强度。
    strict\text{strict}
  2. false → true 迁移路径迁移不是一步到位,而是先关闭严格模式让老代码通过,等新代码稳定后再逐步打开。
    false⇒true\text{false} \Rightarrow \text{true}
  3. boolean 类型它只有“开”和“关”两个取值,但开启的过程中可以配合 any、allowJs、checkJs 做阶段式过渡。
    boolean\text{boolean}

AI 应用场景

大模型响应的安全解析

阶段:数据解析与校验(AI 应用的输入输出管道)

解决的问题:大模型返回的 JSON 结构不稳定,字段可能缺失或类型漂移。传统 JS 直接 res.data.items.map(...) 取值,一旦结构不对就在运行时崩溃,用户看到的是一张白屏。用 TypeScript 先把响应形状定义成 interface,再用联合类型处理“成功/失败”分支,类型错误在编译期就会暴露。

具体案例:一个聊天机器人前端,定义 type ChatMsg = { role: "assistant" | "user"; content: string } | { role: "system"; notice: string },用可辨识联合按 role 分流渲染。即使模型返回了意料之外的字段结构,编译期就能发现类型不符,而不是等到线上崩溃。

前后端接口对齐的自动报警

阶段:模型服务接口开发与联调

解决的问题:推荐系统的后端把 userId 从 number 改成 string,前端靠肉眼几乎不可能在每个调用点发现。用 interface 定义 API 响应合同,后端字段一旦变化,前端编译结果立刻标红,检查站代替人工排查。

具体案例:某推荐系统上线前,后端把订单金额字段从 price 重命名为 amount 并改成字符串。前端所有引用 Order["price"] 的地方在构建时全部报错,开发按错误清单批量修正,联调阶段零事故。

多端 AI 产品的共用类型约定

阶段:多端协作开发(小程序 / Web / Node 服务)

解决的问题:同一个 AI 产品在 Web、小程序、服务端三端都要处理相同的数据结构,各写各的解析逻辑极易漂移。把数据结构收拢成一份 type/interface 定义,作为三端共用的“规格合同”,任何一端修改都触发全链路编译检查。

具体案例:一个 AI 绘画产品的三端代码库,公共类型包定义了任务状态 type TaskState = "pending" | "running" | "done" | "failed",三端共同引用。后端新增状态时,前端 switch 分支漏写直接编译报错,避免了发布后“状态展示空白”的线上事故。

Python 演示

python53 行
import sysfrom typing import Any, Dict, List  class SimpleTypeChecker:    """一个迷你类型检查站: 模仿 TypeScript 在运行前核对变量类型。"""     def __init__(self) -> None:        self.type_env: Dict[str, type] = {}        self.errors: List[str] = []     def infer(self, value: Any) -> type:        """类型推导: 像老师傅那样根据值直接判断出类型。"""        return type(value)     def declare(self, name: str, value: Any) -> None:        """声明变量: 不写标签, 由初始值推导出类型。"""        self.type_env[name] = self.infer(value)        print(f"[OK] 声明 {name}: {self.infer(value).__name__}")     def assign(self, name: str, value: Any) -> None:        """赋值检查: 新值必须与已推导的类型一致, 否则拦截。"""        if name not in self.type_env:            self.errors.append("未知变量 " + name)            return        expect: type = self.type_env[name]        actual: type = self.infer(value)        if expect != actual:            self.errors.append(                f"[拦截] {name} 期望 {expect.__name__}, 实际 {actual.__name__}"            )        else:            print(f"[OK] {name} 更新为 {value!r}")     def report(self) -> None:        """输出检查站拦截清单。"""        if not self.errors:            print(">>> 全部通过, 放行!")            return        print(">>> 检查站拦截清单:")        for err in self.errors:            print(err)  if __name__ == "__main__":    checker = SimpleTypeChecker()    checker.declare("age", 18)    checker.declare("name", "tom")    checker.assign("age", 19)    checker.assign("name", "bob")    checker.assign("age", "十九")    checker.assign("age", None)    checker.report()
目的:用 Python 模拟 TypeScript 的静态类型检查流程——推导变量类型、核对每次赋值、输出“编译期”拦截报告。
  1. self.type_env: Dict[str, type] = {}一张“变量 → 类型”登记表,相当于检查站的台账,记录每个变量当前的类型。
  2. def infer(self, value: Any) -> type:定义“老师傅直觉”:输入一个值,返回它的类型,对应 TS 的类型推导。
  3. self.type_env[name] = self.infer(value)声明变量时自动推导类型并登记,对应 let age = 18 被推导为 number。
  4. if expect != actual:新值与登记类型不一致时记入拦截清单,对应 TS 编译期的类型报错。
  5. checker.assign("age", "十九")用字符串给 number 型变量赋值,被检查站拦截并记录错误。
  6. checker.report()输出本次“编译期检查”的完整结果,对应 TS 编译器打印的错误列表。

常见误区

误区我用了 TypeScript,类型检查全部通过,程序运行时肯定不会再有 bug 了。

正解类型系统只抓“类型错误”,不抓业务逻辑错误。检查站只负责核验“标签和实物是不是一类货”,不保证客户真的喜欢这批货。任何、类型断言都能绕过检查,逻辑写错、接口返回异常、并发问题依然会在运行时出现。TS 的价值是把“一类常见错误”提前拦住,而不是担保程序绝对正确。

为何常见:把“静态检查”误当成“正确性保证”,把类型系统的作用范围想得太宽了。

误区`let x: string | number` 声明之后,x 既能调用字符串的方法,又能调用数字的方法,随便用。

正解联合类型是“或”不是“且”,x 在任意时刻只能是其中一种。检查站只知道“可能是文本也可能是数字”,所以在收窄之前,只允许调用两种类型都支持的通用操作;想用 toUpperCase() 这种字符串专属方法,必须先 typeof 收窄,确认它此刻确实是 string。

为何常见:日常语言里“或者”有时带着“兼有”的模糊感,初学者容易把“|“理解成”和“,而”和”其实是交叉类型的语义。

误区`A & B` 比 A 和 B 都厉害,它包含了 A 或 B 中任意一个的所有能力,随便挑着用。

正解交叉类型不是“挑大的用”,而是“全都要”:对象必须同时具备 A 和 B 的所有属性,少一个都不合格。string & number 这种互相冲突的组合,结果不是“更大”而是“空”——never,没有任何值能满足它,就像做不出“既是液体又是固体”的箱子。

为何常见:用联合类型的“或”直觉去套交叉类型,把“∪“和”∩”的语义彻底搞反了。

误区反正都是描述类型的语法,interface 和 type 没有任何区别,选哪个全看心情。

正解两者有明确分工:type 是“万能的别名”,能表达联合、交叉、元组、映射等更广的结构;interface 是“对象合同”,支持声明合并(同名自动合并)、可以被 extends 继承,在公共 API 库中更受青睐。团队惯例通常是“描述对象形状优先 interface,需要联合/交叉/元组时用 type”。

为何常见:在小项目里两者表现几乎一样,很多人没碰到“声明合并”“元组类型”这类差异边界,就误以为永远等价。

误区TS 会自动推导类型,那我就什么都不标,让编译器自己猜,代码最简洁。

正解推导的前提是“有线索”。函数参数没有初始值、复杂嵌套结构推导结果可能不够精确、跨模块的公共边界需要稳定的显式契约——这些位置不标注,类型就退化成隐式 any,检查站形同虚设。正确的姿态是“能推导的地方少写,边界处写清楚”,把注解留给参数、返回值和对外接口。

为何常见:体验过 let age = 18 被自动推导成 number 之后,容易高估推导能力,忽略了“没有线索时推导无能为力”的前提。

面试题

Q1. TypeScript 给 JavaScript 带来的最核心能力是什么?

单选题 · easy

  • A.让代码运行速度更快
  • B.编译期静态类型检查← 正确答案
  • C.自动完成垃圾回收
  • D.提供全新浏览器接口

答案:B。类型系统真正的价值是在编译期(发货前)把类型错误拦下来,就像检查站核验货物标签。A 与运行时性能无关,C 是 JS 引擎自带能力,D 与类型检查无关。

Q2. 下面哪种写法能正确声明一个 number 类型的变量 age?

单选题 · easy

  • A.let age: number = 25← 正确答案
  • B.let number: age = 25
  • C.let age = number 25
  • D.var age := 25 number

答案:A。: number 是类型注解,相当于给变量贴上“数字类”标签。B 把关键字和变量名写反了,C、D 语法不合法,检查站根本读不懂。

Q3. 联合类型 let id: string | number 允许给 id 赋什么值?

单选题 · mid

  • A.只能是字符串类型
  • B.只能是数字类型
  • C.字符串或数字皆可← 正确答案
  • D.同时是两者类型

答案:C。竖线表示“或”,是二选一通道,C 对。A、B 只看到一边;D 把“或”理解成“且”,那其实是交叉类型的语义,最容易混淆。

Q4. 交叉类型 type T = A & B 得到的类型有什么特点?

单选题 · mid

  • A.要么是 A 要么是 B
  • B.同时拥有两者全部属性← 正确答案
  • C.只保留 A 的属性部分
  • D.自动生成全新父类型

答案:B。& 表示“全都要”,像既防水又防火的箱子,两边规格都必须满足,B 对。A 是联合类型语义,C、D 是常见误解。

Q5. 关于 type 与 interface 的区别,下列说法正确的有?

多选题 · mid

  • A.type 可以定义联合类型← 正确答案
  • B.interface 支持声明合并← 正确答案
  • C.type 可以给交叉类型起名← 正确答案
  • D.interface 可以表示元组类型

答案:ABC。type 是“万能的别名”,联合、交叉、元组都能表达,A、C 对;interface 是“对象合同”,同名声明会自动合并,B 对。元组只能用 type 表示,D 错。

Q6. 在 let x = 42 这句代码里,TypeScript 推导出 x 是什么类型?

单选题 · easy

  • A.自动推导为 number← 正确答案
  • B.自动推导为 string
  • C.自动推导为 any 类型
  • D.编译器会直接报错

答案:A。推导就是老师的直觉:看到初始值 42 就知道是数字货,A 对。只有完全找不到线索时才会退化成 any。

Q7. 接口中 age?: number 表示什么意思?

单选题 · mid

  • A.该属性必须存在且为数字
  • B.该属性可以有也可以没有← 正确答案
  • C.该属性只能是 undefined
  • D.该属性只能是 null 类型

答案:B。问号是“可选条款”的标记:合同允许缺这一项,若有则必须是数字,B 对。A 是必选属性,C、D 把可选与空值混为一谈。

Q8. 迁移老代码时给变量标注 any 类型,会发生什么?

单选题 · mid

  • A.跳过该变量的类型检查← 正确答案
  • B.强制所有值变成字符串
  • C.运行时必定抛出错误
  • D.自动推导出精确类型

答案:A。any 是“临时通行证”,盖上它检查站直接放行,A 对。它用来平滑迁移,最终要逐步换回精确类型,而不是长期保留。

Q9. 下列属于 TypeScript“类型收窄”手段的有?

多选题 · hard

  • A.使用 typeof 判断分支← 正确答案
  • B.使用 in 检查对象属性← 正确答案
  • C.判断值是否为 null← 正确答案
  • D.使用 as 强制指定类型

答案:ABC。收窄是用条件判断把“二选一”变成“确定是哪一个”,typeof、in、空值判断都是常见方式,A、B、C 对。as 是“断言”,是强行盖章,不是依靠条件判断收窄,D 错。

Q10. 严格模式下,function f(n: number){} 传入 undefined 会怎样?

单选题 · hard

  • A.编译期报错提示类型不符← 正确答案
  • B.运行期自动转为字符串
  • C.编译期自动补默认值
  • D.不会做任何检查提示

答案:A。严格模式下 null/undefined 不能流入 number 参数,检查站在发货前就会拦截,A 对。这正是“编译期检查”与“运行时才知道”的差别。