JAVASCRIPT与TYPESCRIPT · KNOWLEDGE CHAPTER
K10

类型建模与编译边界

TypeScript 在程序执行前检查我们声明的关系,JavaScript 在执行时处理真实值。类型系统能拒绝许多不一致用法,却不会自动验证网络数据、冻结对象或替服务端鉴权。学习类型建模的关键,是明确哪些信息仅存在于编译器,哪些语言特性会生成或保留运行时行为。

JavaScript与TypeScript2 道章节练习7 道主练关联

核心模型

TypeScript 在程序执行前检查我们声明的关系,JavaScript 在执行时处理真实值。类型系统能拒绝许多不一致用法,却不会自动验证网络数据、冻结对象或替服务端鉴权。学习类型建模的关键,是明确哪些信息仅存在于编译器,哪些语言特性会生成或保留运行时行为。

原理与机制

结构兼容不是精确对象验证

TypeScript 主要按成员结构判断兼容,类型名称相同与否通常不是关键。新鲜对象字面量有额外属性检查,先存到变量后传递可能按结构兼容通过,额外字段没有因此在运行时被删除。interface 适合对象契约和声明合并,type 还便于联合、元组等表达;并非谁全面更强。类型注解与多数断言会擦除,fetch 返回值写 as User 只是承诺。外部输入应先视为 unknown,执行真实校验并挑选允许字段;any 会绕过检查并把不确定性传播给后续代码。

收窄应来自真实检查,穷尽应反映状态空间

typeof、instanceof、in 和字面量比较把宽类型逐步收窄。unknown 是暂不能安全使用的值,never 表示不可能存在的值,void 则表达不使用返回结果,不能互换。把任务建成 idle/loading/success/error 的判别联合,让每支携带必需字段,再用 never 检测新增成员的遗漏。自定义 is 或 asserts 声明由编译器信任,实现不正确仍会失守。catch 可收到字符串或 null,至少先排除 null、核查 message 类型并兜底;跨 realm 的 Error 可能不通过当前 instanceof,getter/Proxy 读取本身也可能抛错。

类型关联与字段缺失都属于协议

type:A|B 与 payload:X|Y 独立声明会接受交叉错配;应构造 {type:A,payload:X}|{type:B,payload:Y},或从事件映射生成关联联合。泛型 K 若被放宽为联合,独立参数也可能丢配对约束,整体判别对象或关联元组更稳。并非任何解构都会丢关联,符合条件的 const 解构可保留控制流分析。补丁同样要明确缺失、undefined、null:exactOptionalPropertyTypes 让 x?:string 的存在值更严格,但读取仍可能 undefined;JSON 会省略对象中的 undefined,清空操作最好用明确 null 或操作类型。

enum 是类型擦除规则中的重要边界

普通 enum 通常生成运行时对象,数字枚举还有反向映射,字符串枚举不具同样行为。const enum 通常内联,减少对象,却把消费者产物绑定到编译时枚举值,跨包版本及 isolatedModules 可能出问题。库可选择普通 enum 或 as const 对象配合值联合;as const 不会调用 Object.freeze。若内部保留 const enum,需实际构建配合 preserveConstEnums,并在发布声明移除 const,让 JS 与声明一致。协议宜用明确稳定字符串,避免数字重排改变持久化含义。

可见性修饰与语言私有元素不是同一种保护

TS public 默认公开,protected 约束类及子类的常规访问,private 约束声明类的常规访问;它们主要提供静态软限制,普通属性编译后通常还在,private 也有方括号访问等逃生口。JavaScript #字段是独立私有元素,按声明名字与接收者品牌检查,不能用 obj["#x"] 取得,也不能由子类直接读基类私有名字。编译目标决定保留语法还是生成私有存储实现。#不保证返回的可变对象引用不可变,更不是秘密保险箱;浏览器中的封装不能替代服务端鉴权。

最小示例

typescript
typescript
type Result =
  | { kind: 'loading' }
  | { kind: 'success'; data: string }
  | { kind: 'error'; error: string };
function describe(result: Result): string {
  switch (result.kind) {
    case 'loading': return '加载中';
    case 'success': return result.data;
    case 'error': return result.error;
    default: {
      const impossible: never = result;
      return impossible;
    }
  }
}
console.log(describe({kind:'success', data:'完成'}));

编译器预期接受 success 与 data 的配对,输出 JavaScript 后运行应显示“完成”。在联合里新增 cancelled 却不补分支,never 赋值应报错。直接把接口响应断言为 Result 不执行任何校验;示例故意只演示已验证内部状态的建模,不能作为解析外部 JSON 的完整实现。

环境:TypeScript 4.9+;建议 tsc --strict --noEmit 检查。本章可选属性实验另外开启 exactOptionalPropertyTypes。

核验:未执行教学示例;说明中的结果为机制推演,不是本轮测试通过记录。

边界与取舍

边界

  • readonly、as const、private 主要是静态约束,不能自动冻结对象或保证数据真实。
  • 缺失与显式 undefined 可被 Object.hasOwn 区分;?? 同时兜底 null 与 undefined,可能破坏清空协议。
  • new Error 不是唯一异常来源;类型断言和错误类型谓词都不会补上缺少的运行时检查。

取舍

  • 判别联合让非法组合更难构造,但后台刷新保留旧数据等真实并存状态也要明确建模,不能机械排除。
  • 普通 enum 提供运行时对象;字符串常量对象更接近普通 JS 工具链,const enum 的产物收益需与发布兼容成本比较。

口述示范

我先讲清 TS 是结构化静态契约,再用判别联合和 never 保留状态及事件关联。外部 unknown 必须真实验证,可选字段还要和 JSON 协议一致。最后单独讨论会影响运行时的 enum 与 #字段,避免把擦除、封装或只读误说成安全保障。

章节练习

练习 1

假设 TranslateFlow 的加载状态同时有 loading、error、data,如何使新增取消态暴露遗漏?

展开检查点
  • 按合法业务状态建立 kind 联合,在 switch 末尾做 never 检查。
  • 若允许旧数据后台刷新,显式表达;事件名称与负载用关联联合整体传递。
练习 2

一个公共包同时发布昵称补丁、状态枚举和类 private 字段,怎样验证边界?

展开检查点
  • 规定缺失不改、null 清空等协议,启用 exactOptionalPropertyTypes 并验证运行时 JSON。
  • 检查 JS 与 d.ts 一致;区分 const enum 内联、as const 静态只读、TS private 与 #运行时私有。

参考来源与核验边界

核验日期 2026-10-04。类型示例以 strict 检查为前提,exactOptionalPropertyTypes 需另行开启;关联 const 解构的控制流能力从 TS 4.6 起提供。不同编译目标与构建器输出应查看实际 JS,不推断所有 TS 语法都擦除。 本章新增示例未在本轮执行,不新增测试通过声明。

内容版本:2026-10-04.2 · 题目和来源 ID 保持原样,本站不将面经标签解释为企业官方出题或高频保证。