某天,快递员小李的仓库彻底乱了套:所有包裹都长得差不多,没有面单、没有标签,他把一箱玻璃杯扔进了重物区,客户收到时碎了一地。问题不在传送带,而在于——东西送出去了才知道错了。JavaScript 的世界就是这样:代码跑起来才发现类型对不上,报错永远发生在最尴尬的运行时。 直到仓库升级成“智能分拣中心”:每个包裹都要先过一道安检闸门,贴上品类标签——红色圆标是数字、蓝色方标是字符串、三角标是布尔值;每件包裹还要贴标准面单,缺字段直接拒收;闸门边的老师傅甚至不用看标签,一眼就能推断出包裹的类型。这套系统就是 TypeScript。它给 JavaScript 加上静态类型系统,在代码运行前就完成全部“安检”,再用 interface 约定对象形状、用泛型做出“不挑类型却记得类型”的万能传送带。 本文就带你在分拣中心里逛一圈,从基础类型到接口、泛型、类型推断,看完你也能给代码穿上这件“安检服”。

岗位关联

掌握 TypeScript 类型系统与泛型,是前端进阶的必经关卡——它让你的代码在编译期就暴露隐患,也为阅读框架源码、参与大型项目和接入 AI 辅助编码打下坚实基础。

目录
  1. 可视化演示
  2. 一、静态类型价值:安检闸门为什么值得建
  3. 二、基础类型入门:四色品类标签
  4. 三、接口契约:标准面单长什么样
  5. 四、泛型入门:万能传送带
  6. 五、泛型约束:限重限尺寸
  7. 六、类型推断:自动贴标的老师傅
  8. 七、工程实践:分拣中心的标准化手册
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示编译期静态类型检查捕获错误、接口定义与泛型类型参数化实例化、以及类型推断与高级类型工具运算三大核心维度。

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

TS类型系统 静态类型检查:编译期发现类型不匹配错误,编译输出无类型的JS 泛型:类型参数化,同一函数可实例化为number/string/object等多种类型 T → number | string | T 类型推断:从值自动推导类型,高级工具Partial/Pick/Omit/Record实现类型变换

一、静态类型价值:安检闸门为什么值得建

快递员小李在旧仓库干了五年,从不给包裹贴标签。直到有一天,客户收到碎屏的笔记本电脑,投诉的电话声把仓库屋顶都快掀翻了。问题出在哪?包裹送出去了才发现错了——这正是 JavaScript 的日常:类型不匹配的 bug,总是等到浏览器运行的那一刻才炸出来。

TypeScript 给仓库加了一道安检闸门。所有代码在“发货”(运行)之前先过检,闸门逐行核对每个变量、每个参数的“类型标签”。这套运行前检查的规则,就叫静态类型系统。与之相对,JavaScript 是动态类型,类型全靠运行时现场判断,就像小李全靠手感分拣。

在 TS 里,给变量贴标签只需要在声明时加一个冒号:let age: number = 25,读作“age 的类型是 number”。这之后如果给它赋字符串,编译器立刻报警。类型检查其实是一套严密的推导规则:拿着已知信息,一步步推出每个表达式的类型。这条规则可以写成下面这样——

有了这道闸门,小李再也不用等客户投诉才发现错误。TS 在代码“发出”前就亮起红灯,把“易碎品混进重物区”这类错误拦在源头。等代码真正上线运行时,每一件包裹都已经过了质检——这正是静态类型最朴素的价值:错误早一点被发现,代价就小一大截。

Γ⊢e:T\Gamma \vdash e : T
这条式子读作:“在类型环境 Γ 下,能推导出表达式 e 的类型是 T。”Γ 好比安检员手边那本已登记包裹的账本,每登记一行,检查就多一条依据。
  1. 类型环境 Γ安检员手边那本“已登记包裹”的账本,记录着所有已声明变量的类型。Γ\Gamma
  2. 推导符号 ⊢读作“推导出”,表示左边条件成立就能推出右边结论。⊢\vdash
  3. 表达式 e待检查的代码片段,比如一个变量名或一次函数调用。ee
  4. 类型归属冒号读作“的类型是”,把值和类型连接成一条完整的判断。e:Te : T
  5. 类型 T推导的结论,number、string 或自定义接口都算。TT

二、基础类型入门:四色品类标签

分拣中心的第一个改革,是给每种包裹贴固定颜色的品类标签:红色圆标代表数字包裹,蓝色方标代表字符串包裹,三角标代表真假开关包裹。员工一眼扫过颜色,就知道该送哪条线,分拣效率翻了一倍。

这就是 TS 的基础类型:number 负责数字、string 负责文本、boolean 负责真假值。声明变量时贴上标签,之后它只能装对应类型的值。比如 let age: number = 25,这只“抽屉”从此只放数字,塞进去一个字符串,编译期就叫你拿回去。

TS 的标签远不止三种。数组是一整箱同色包裹,写作 number[],里面全是数字;元组是格子固定的组合箱,[string, number] 规定第 1 格放字符串、第 2 格放数字。还有 null 和 undefined 两个“空包装袋”,分别表示“没有值”和“还没定义”。每个值到底属于哪一类,可以看作一个取类型函数——

有了这套四色标签,分拣员的目光从“包裹长得像什么”变成“包裹贴了什么标”。代码里每个量都有了固定颜色,想混装?安检闸门第一个不答应。这就是基础类型给你的第一层安全感。

type(42)=number;type("hello")=string;type(true)=boolean\text{type}(42) = \text{number};\quad \text{type}(\text{"hello"}) = \text{string};\quad \text{type}(\text{true}) = \text{boolean}
每个具体值都能查出唯一的“品类”。这就是基础类型系统建立的分类表:一值一类型,绝不混淆。
  1. 取类型函数 type(·)把“给包裹贴品类标签”抽象成数学函数,输入一个值,输出它的基础类型。type(⋅)\text{type}(\cdot)
  2. 数字归 number所有数值型包裹统一贴红色圆标。type(42)=number\text{type}(42) = \text{number}
  3. 字符串归 string所有文本型包裹统一贴蓝色方标。type("hello")=string\text{type}(\text{"hello"}) = \text{string}
  4. 布尔归 boolean所有真假二值包裹贴三角标。type(true)=boolean\text{type}(\text{true}) = \text{boolean}

三、接口契约:标准面单长什么样

光有品类标签还不够。仓库规定,每件包裹必须贴一张标准面单:收件人姓名(name)、重量(age)、联系电话(phone)。面单缺了必填项,安检直接拒收——这是仓库和客户之间达成的默契。

TS 用 interface 描述这种“面单标准”。它不关心包裹里装了什么值,只约定对象必须长成什么形状。name: string 表示该字段必须是字符串,email?: string 的问号表示可选——就像面单上的“备注”栏,可填可不填。

接口还能加 readonly 修饰符,声明只读字段:readonly id: number 表示一旦赋值就不能改,防止订单号被悄悄篡改。接口更常见的使用场景是约束函数入参:声明函数“请给我一个 Person”,任何调用者都必须提交符合这张面单的对象,否则编译期拒收。接口的完整样式如下——

有了标准面单,分拣中心和大客户之间就有了共同语言。前端对接后端接口、团队之间传递对象,全靠这份模板兜底——谁少填了字段、谁把类型写错,编译期一眼就能揪出来。

Person≡{ name:string, age:number, email?:string, readonly id:number }\text{Person} \equiv \{\, \text{name}: \text{string},\ \text{age}: \text{number},\ \text{email}?: \text{string},\ \text{readonly id}: \text{number} \,\}
接口是一份纯“结构约定”,编译后被彻底抹掉,运行时根本不存在——它只活在编译期,替你把关每一张面单。
  1. 接口名 Person这是面单模板的编号,安检员看到它就知道该找哪张标准。Person\text{Person}
  2. 结构等价符号接口不是运行时对象,只是“面单长这样”的纯结构约定。≡\equiv
  3. 必填字段冒号前是字段名,冒号后是字段类型,缺一不可。name:string; age:number\text{name}: \text{string};\ \text{age}: \text{number}
  4. 可选与只读问号表示字段可省略,readonly 表示字段赋值后不可修改。email?:string; readonly id:number\text{email}?: \text{string};\ \text{readonly id}: \text{number}

四、泛型入门:万能传送带

分拣中心常接到一种麻烦需求:要一条传送带,不挑包裹类型——数字、字符串、自定义对象,原封不动送过去就行。工程师最初想:那干脆不贴标签算了!可标签一撕,包裹在路上就丢了身份,送到哪都说不清。

这正是“泛型”要解决的问题。泛型函数用尖括号声明一个占位类型 T,函数体内 T 代表“当前这趟的实际类型”。只要入参和返回值挂同一个 T,类型关系就一路保住了——通用,但不丢身份。

TS 标准库里有个经典例子:identity 函数。它不做任何加工,把 x 原样返回,类型也原样保留:

万能传送带每天处理上万件包裹,从不过问“你是谁”,却始终知道每一件是谁。这就是泛型的魅力:把“通用处理”和“类型安全”这对看似矛盾的需求,优雅地焊在了一起。

identity: ∀T∈Type. (x:T)→T\text{identity}: \ \forall T \in \text{Type}.\ (x: T) \to T
读作“对任意类型 T,identity 接收一个 T 并返回一个 T”。用 any 也能写出同样效果的函数,但 any 等于撕掉所有标签,类型信息全部丢失;泛型则让类型关系在每一站都被记住。
  1. 全称量词 ∀读作“对于任意一个 T”,表示这条传送带对一切类型一视同仁。∀T\forall T
  2. 类型参数 TT 是传送带上的“空位牌”,调用时才知道具体装什么。T∈TypeT \in \text{Type}
  3. 函数类型箭头左边是入参类型,右边是返回类型,T 原样进出。(x:T)→T(x: T) \to T
  4. 调用实例化调用 identity<number>(42) 时,T 被替换成 number,结果类型也锁定为 number。identity⟨number⟩(42):number\text{identity}\langle \text{number}\rangle(42) : \text{number}

五、泛型约束:限重限尺寸

万能传送带也不是什么都收。中间有条支线,专门给“能测长度的包裹”——字符串、数组——称重、计件。数字包裹没有 length 属性,硬塞进去只会把机器卡住。机器得学会说“不”。

TS 用 extends 给类型参数加约束。T extends HasLength 的意思是:T 必须是 HasLength 的子类型,也就是必须带 length 属性。这就像传送带入口挂一块限重牌,超规包裹自动弹出。

用集合的语言说,约束就是在定义一个“合格类型俱乐部”:

有了限重牌,支线传送带再也没卡过壳。约束不是限制自由,而是提前告诉机器“你能接的活有哪些”。通往安全的路上,边界从来不是敌人——它是护栏。

HasLength≡{ T∣T 具有 length:number }\text{HasLength} \equiv \{\, T \mid T \text{ 具有 } \text{length}: \text{number} \,\}
只有满足“具有 number 类型的 length 属性”的成员,才能进这条支线。字符串、数组都符合,纯数字被拒之门外。此外,keyof 能从对象类型里“抠”出所有键名组成联合类型,常用在泛型约束里做键筛选;类型参数还能设默认值 <T = string>,调用方不传类型时自动使用默认值。
  1. 约束接口 HasLength这是传送带入口贴的限重牌,写着“必须能测长”。HasLength\text{HasLength}
  2. 集合描述法花括号里的竖线读作“满足……条件”,整体表示所有合法类型。{ T∣⋯ }\{\, T \mid \cdots \,\}
  3. 必备属性进入传送带的类型必须带一个 number 类型的 length 属性。length:number\text{length}: \text{number}

六、类型推断:自动贴标的老师傅

干了十年的老师傅,包裹在传送带上一晃,他不用看标签就能报出品类:红色圆标?数字!蓝色方标?字符串!经验让识别变成了肌肉记忆——标签还没贴,他先猜了个八九不离十。

TS 也这样。你写 let count = 42,不用手动加 : number,编译器根据初始值自动推断出 count 是 number。函数的返回值同样能推断:function add(a: number, b: number) { return a + b; },TS 知道它返回 number。这就是类型推断——让标签自己长出来。

在更复杂的场景里,推断遵循严格的推理规则。函数调用是最常见的一种:如果能确定函数的入参出参类型、又能确定实参类型,那么调用结果类型就顺理成章地推出来了——

但老师傅也有拿不准的时候:一件包裹既像信封又像卡片,到底走哪条线?TS 用联合类型表达这种“二选一”,写作 string | number——可以是字符串,也可以是数字。遇到联合类型,代码里得先用 typeof 或 in 这类“类型守卫”看一眼,把范围收窄到具体分支,才能调用该类型特有的方法。这就像老师傅拆开包裹一角确认一下,再决定分拣方向。

推断负责省力,守卫负责把关。老师傅的经验最终成了年轻分拣员的自动导航:类型表达得越清楚,TS 就能帮你挡住越多手滑——这就是类型系统温柔又严苛的一面。

Γ⊢f:A→BΓ⊢x:AΓ⊢f(x):B\dfrac{\Gamma \vdash f : A \to B \quad \Gamma \vdash x : A}{\Gamma \vdash f(x) : B}
横线上方是两个前提:f 是把 A 加工成 B 的函数、x 是 A 类型的实参;下方是结论:f(x) 的结果一定是 B。这条规则就像链条上的齿轮,一环咬住一环。
  1. 推理横线分数线上面是前提,下面是结论——前提成立则结论必成立。前提结论\dfrac{\text{前提}}{\text{结论}}
  2. 函数类型前提f 是一个把 A 型包裹加工成 B 型包裹的机器。Γ⊢f:A→B\Gamma \vdash f : A \to B
  3. 参数类型前提x 恰好是 A 型包裹,和机器入口匹配。Γ⊢x:A\Gamma \vdash x : A
  4. 结论把 x 投入 f 得到的结果,一定是 B 型包裹。Γ⊢f(x):B\Gamma \vdash f(x) : B

七、工程实践:分拣中心的标准化手册

分拣中心越开越大,光靠老师傅带徒弟不够了,得把经验写进标准化手册。在 TS 的世界里,这本手册最重要的两章,正是接口和泛型。

第一场景:对接后端 API。用 interface 声明响应数据结构,字段名、类型一目了然;后端悄悄改了字段,前端编译期立刻报错。接口把“后端承诺返回什么”变成了代码里看得见摸得着的契约,再也不怕接口偷偷“瘦身”。

第二场景:抽公共工具函数。类型参数化让一个函数服务多种类型,前端框架的源码里更是到处是泛型和工具类型的组合。比如 Pick,能从一个大对象类型里挑出你需要的字段,生成一个更小的类型:

手册的最后一页写着:类型系统不是负担,是仓库的护栏。用接口约定数据形状,用泛型抽象公共逻辑,再让推断帮你省掉重复标注——大型项目里的每个成员,写出的代码都像同一个老师傅的手笔。分拣中心,从此告别“发货即事故”。

Pick(T,K)≡{ k:T[k]∣k∈K }\text{Pick}(T, K) \equiv \{\, k : T[k] \mid k \in K \,\}
读作“从 T 中挑选键集合 K 对应的字段,组成新类型”。它是接口与泛型组合的经典应用,也是 TypeScript 内置的工具类型。
  1. 工具类型 Pick像一张“面单裁剪模板”,从完整面单里挑出需要的字段。Pick(T,K)\text{Pick}(T, K)
  2. 源类型 T被裁剪的原始面单。TT
  3. 键集合 K要保留的字段名清单。KK
  4. 逐键保留对 K 中每个键 k,保留下它对应的类型。{ k:T[k]∣k∈K }\{\, k : T[k] \mid k \in K \,\}

AI 应用场景

前端 API 数据契约管理

阶段:数据接入与数据校验(工程化阶段)

解决的问题:传统前后端联调靠文档和人肉核对,后端改字段名、改类型,前端毫无感知,直到上线才崩溃。用 interface 把响应结构写成代码里的“面单契约”,编译期就能发现字段缺失和类型漂移。

具体案例:某团队对接订单查询接口,用 interface Order { id: number; total: number; items: OrderItem[] } 声明返回结构。后端某天把 total 悄悄改成了 totalPrice,前端仓库编译立刻报错,避免了一次线上事故。整个排查过程就像安检闸门红灯亮起:不用等客户拆箱,错误在仓库里就被拦下。

组件库与工具函数的泛型设计

阶段:代码复用与抽象设计

解决的问题:做组件和工具函数时,用 any 图省事会丢失全部类型提示;泛型让一个函数服务所有类型,还不丢入参与返回值的类型关系。

具体案例:团队写了一个 useRequest<T>(url: string): { data: T | null } 的 Hook,调用时传入类型参数就能获得完整的类型提示。在 AI 辅助编码的场景里,类型越完整,IDE 补全越准确,AI 生成的代码也越少踩坑——万能传送带越规范,机械臂越不容易抓错包裹。

AI 辅助编码工具的类型感知

阶段:智能编码与代码补全

解决的问题:Copilot 等 AI 编码工具依赖语言服务协议(LSP)提供的类型信息做补全、跳转和重构。TypeScript 类型越完整,AI 生成代码的错误率越低。

具体案例:在一个泛型组件库中,AI 助手通过“泛型约束”自动推荐符合 extends 条件的调用方式——比如只给带 length 属性的类型推荐测长函数,减少反复试错。这就像老师傅把“看一眼再分拣”的经验教给了机械臂。

Python 演示

python76 行
# -*- coding: utf-8 -*-"""迷你 TypeScript 类型检查器:模拟接口结构校验与泛型约束"""from typing import Any, TypeVar  class Field:    """等价于 TS 接口中的一个字段定义"""     def __init__(self, name: str, expect: str, required: bool = True):        self.name = name        self.expect = expect        self.required = required     def check(self, value: Any) -> bool:        if self.expect == "number":            return isinstance(value, (int, float)) and not isinstance(value, bool)        if self.expect == "string":            return isinstance(value, str)        if self.expect == "bool":            return isinstance(value, bool)        if self.expect == "null":            return value is None        return True  class Interface:    """等价于 TS 的 interface:约束对象的形状"""     def __init__(self, name: str, fields: list):        self.name = name        self.fields = fields     def validate(self, obj: dict) -> list:        errors = []        for f in self.fields:            if f.name not in obj:                if f.required:                    errors.append("缺少必填字段: " + f.name)                continue            if not f.check(obj[f.name]):                actual = type(obj[f.name]).__name__                errors.append(f.name + " 期望 " + f.expect + ", 实际是 " + actual)        return errors  T = TypeVar("T")  def identity(x: T) -> T:    """对应 TS 泛型函数: function identity<T>(x: T): T"""    return x  def echo_len(x: Any) -> int:    """对应泛型约束: T extends { length: number }"""    if not hasattr(x, "__len__"):        raise TypeError("该类型没有 length 属性, 不满足泛型约束")    return len(x)  if __name__ == "__main__":    Person = Interface("Person", [        Field("name", "string"),        Field("age", "number"),        Field("email", "string", required=False),    ])    samples = [        {"name": "Alice", "age": 25},        {"name": "Bob", "age": "未知"},        {"age": 30},    ]    for i, obj in enumerate(samples):        errs = Person.validate(obj)        print("样本", i, "检查通过" if not errs else "报错: " + str(errs))    print("identity(42) =", identity(42))    print("echo_len([1,2,3]) =", echo_len([1, 2, 3]))
目的:用 Python 模拟 TypeScript 的核心思想——接口结构校验和泛型类型关系。它能像“面单标准”一样检查对象是否满足字段要求,并用泛型函数保留入参和返回值的类型约定。
  1. class Interface:相当于 TS 的 interface,一个“面单模板”。
  2. def validate(self, obj: dict) -> list:返回错误列表而非直接抛异常,一次性报告所有问题,像安检闸门一次性列出入场缺项。
  3. if f.name not in obj:检查必填字段是否存在,对应 TS 的必填字段校验。
  4. if not f.check(obj[f.name]):调用 Field 的类型检查方法,验证字段值类型是否匹配。
  5. T = TypeVar("T")Python 标准库的泛型类型变量,等价于 TS 的 <T>。
  6. def identity(x: T) -> T:完整的泛型函数签名,入参和返回值共用同一个 T,类型关系不丢失。
  7. def echo_len(x: Any) -> int:对应 TS 的 T extends { length: number } 约束写法。
  8. if not hasattr(x, "__len__"):检查对象是否带 length 属性,不满足就抛错,像限重牌拒收超规包裹。
  9. Person = Interface("Person", [...])定义一个 Person 接口,含必填字段 name、age 和可选字段 email。
  10. errs = Person.validate(obj)对每个样本执行结构校验并输出报告。

常见误区

误区初学者写 TS 时以为和 JS 一样,类型错了只有运行时报错才能发现,所以遇到问题就拼命加 console.log 调试。

正解TS 是静态类型系统,编译器在编译期就完成检查。好比快递在新仓库就过安检,而不是等客户拆箱才发现物品损坏。几乎全部类型错误在 tsc 编译时就亮红灯,根本到不了运行阶段。

为何常见:因为习惯了 JS 时代“运行时才报错”的节奏,没意识到 TS 新增了一道发货前的检查环节。

误区以为 interface 和 class 一样能 new、能在运行时访问属性,“反正编译完都还在”。

正解interface 是纯类型层构造,编译后直接抹除,运行时根本不存在;class 才是“类型 + 实现”。用比喻说:interface 是贴在墙上的面单标准图,class 是打印面单的机器——前者是规则,后者是工具。

为何常见:两者都描述“对象长什么样”,初学者容易混淆“规则”和“实物”。

误区“反正都是不确定类型,用 any 不也一样?泛型就是写法更绕的 any。”

正解any 相当于撕掉标签的包裹,进闸门不检查、出去也不留痕迹,类型关系全丢;泛型 T 则全程带着占位牌,一旦调用时确定具体类型,入参和返回值的关系仍被严格校验。万能传送带虽然不挑货,但每件货的编号都记得清清楚楚。

为何常见:两者在“不限定具体类型”这一点上表面相似,容易忽略泛型对类型关系的保留。

误区以为编译通过就等于代码一定不会崩,于是把 TS 当成免死金牌,null 随便访问、接口字段随便省略。

正解静态类型只能检查类型层面的错误,运行时错误(空值访问、网络超时、越界)依然存在。TS 就像面单检查,只保证面单格式对,不保证箱子里的货物路上不碎。null 和 undefined 的访问要靠可选链等写法配合,不能指望编译通过就万事大吉。

为何常见:把“类型正确”误等同于“逻辑正确”,忽略了静态检查存在边界。

面试题

Q1. TypeScript 相比 JavaScript 最大的新增能力是什么?

单选题 · easy

  • A.让代码体积变得更小
  • B.编译期静态类型检查← 正确答案
  • C.让浏览器直接读懂 TS
  • D.自动优化代码运行速度

答案:B。TS 的核心价值就是在编译期做静态类型检查,把错误挡在运行之前,相当于给仓库加安检闸门。A 是打包压缩工具的事,C 是浏览器兼容策略,D 与类型系统无关。

Q2. 下列哪个是合法的 TypeScript 变量声明?

单选题 · easy

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

答案:A。TS 类型标注的语法是“变量名冒号类型等于值”。B 把等号和类型顺序搞反,C 用了双冒号,D 把关键字放错位置。就像面单上“收件人:张三”,写反了安检员就看不懂。

Q3. interface 在 TypeScript 中的主要作用是什么?

单选题 · mid

  • A.在运行时创建新的对象实例
  • B.描述对象数据结构的形状← 正确答案
  • C.代替 class 完成继承功能
  • D.提升代码的运行时性能

答案:B。interface 是纯结构约定,描述对象应有的字段和类型,编译后就被抹掉。它不能 new 实例,不负责继承(那是 extends 的职责),也影响不了运行时性能。它就是那张贴在墙上的标准面单图。

Q4. 接口中的可选属性使用哪个符号标记?

单选题 · mid

  • A.用问号紧跟在属性名后← 正确答案
  • B.用感叹号紧跟在属性名后
  • C.用冒号紧跟在属性名后
  • D.用小括号包住整个字段

答案:A。age?: number 用问号标记可选字段,表示该字段可以省略;感叹号是非空断言,冒号表示必填类型,括号没有这个含义。可选字段就像面单上的备注栏,可填可不填。

Q5. 泛型函数 function wrap<T>(x: T): { value: T } 返回什么?

单选题 · mid

  • A.返回一个包含原值的对象← 正确答案
  • B.返回值为 null 的空对象
  • C.返回把所有类型转成数字
  • D.返回一个固定的字符串值

答案:A。{ value: T } 表示返回一个对象,里面的 value 字段类型和入参 x 一致。泛型保证了“结果对象里的值就是那个 T”,不会做任何类型转换。B、C、D 都破坏了通用包装函数的类型承诺。

Q6. 泛型约束 T extends { length: number } 表示什么意思?

单选题 · hard

  • A.T 必须恰好是 length 值类型
  • B.T 必须带有 length 属性← 正确答案
  • C.T 只能是 number 类型
  • D.T 必须是字符串类型

答案:B。泛型约束把 T 限制在“带 number 类型 length 属性”的范围内,字符串和数组都满足,纯数字不满足。它既不是要求 T 恰好等于某属性类型,也不限定为单一类型。就像限重牌只检查“有没有测量杆”,不关心包裹具体是哪种。

Q7. keyof 操作符的作用是什么?

单选题 · hard

  • A.取出对象所有键名的类型← 正确答案
  • B.给对象增加一个新的键名
  • C.删除对象中的某个键名
  • D.读取函数执行的耗时数据

答案:A。keyof Person 会返回 Person 所有键名组成的联合类型,常配合泛型做键筛选。它只活在类型层,不会修改运行时的对象。B、C 是运行时增删操作,D 与类型系统完全无关。

Q8. 类型推断指的是什么能力?

单选题 · easy

  • A.根据初始值自动推导类型← 正确答案
  • B.运行时动态修改变量类型
  • C.强制要求标注所有类型
  • D.把字符串自动变成数字

答案:A。类型推断是 TS 根据变量初始值或函数返回值自动推导类型,let count = 42 自动推断为 number。它不是运行时行为,不强制手动标注,更不会转换值。就像老师傅一眼认出包裹品类,不用等标签贴好。

Q9. 下列属于 TypeScript 基础类型的有哪些?

多选题 · mid

  • A.number 数字类型← 正确答案
  • B.string 字符串类型← 正确答案
  • C.boolean 布尔类型← 正确答案
  • D.namespace 命名空间类型

答案:ABC。number、string、boolean 是三大基础类型,对应数字、文本、真假值,是四色标签里的三原色。namespace 是组织模块代码的关键字,不是基础类型。四色标签里没有“namespace 色”。

Q10. 关于泛型和类型推断,下列说法正确的有哪些?

多选题 · hard

  • A.泛型能保留入参和返回值的类型关系← 正确答案
  • B.类型推断可以减少手动的类型标注← 正确答案
  • C.联合类型用竖线连接多个类型← 正确答案
  • D.any 与泛型的类型约束效果完全相同

答案:ABC。泛型让入参和返回值共用同一个类型参数,关系不丢;推断让初始值和返回值自动被推导;联合类型用 | 表达“二选一”,都对。D 错在 any 会彻底放弃类型检查,和泛型天差地别。