any、unknown 和 never 有什么区别?
简化版
any 会关闭类型检查,既能赋给别人也能被别人赋值;unknown 表示未知类型,使用前必须收窄,更安全;never 表示永远不会出现的值,常用于不可能返回的函数和穷尽检查。
详细版
any:
let a: any = 1;
a.foo.bar(); // 编译不报错
unknown:
let u: unknown = 1;
// u.toFixed(); // 报错
if (typeof u === 'number') {
u.toFixed();
}
never:
function fail(msg: string): never {
throw new Error(msg);
}
面试中要强调:能用 unknown 就别用 any,因为 unknown 逼你做类型收窄。
完整版教学
一、any 是类型系统的逃生门
any 的危险在于它会传染。一旦一个值是 any,后续访问属性、调用方法、赋值都绕过检查。
这在迁移老项目时有用,但在新代码中滥用会让 TypeScript 退化成 JavaScript。
二、unknown 是安全的未知
unknown 表示我现在不知道它是什么,但在使用前必须证明。
function parse(input: string): unknown {
return JSON.parse(input);
}
解析 JSON 的结果天然是不可信的,用 unknown 比 any 更合理。调用方必须通过 typeof、Array.isArray、自定义类型守卫等方式收窄。
三、never 表示不可能
never 常出现在两类地方:函数永远不返回,或者类型收窄后已经没有剩余可能。
type Shape = { kind: 'circle'; r: number } | { kind: 'square'; w: number };
function area(s: Shape) {
switch (s.kind) {
case 'circle': return Math.PI * s.r * s.r;
case 'square': return s.w * s.w;
default:
const _exhaustive: never = s;
return _exhaustive;
}
}
如果以后新增形状,default 里的 never 检查会提醒你漏处理。
四、工程建议
外部输入用 unknown,内部逐步收窄;临时迁移不得不用 any 时,要限制范围,别让 any 穿透到核心类型。
never 则是写类型安全分支时的好工具。它能让“不可能路径”进入类型系统,在联合成员扩展后及时暴露遗漏。
五、面试追问与工程落地
高频追问是“unknown 能不能赋给其他类型”。unknown 可以接收任何值,但不能直接赋给具体类型,除非先收窄或断言。any 对绝大多数类型都可双向赋值并跳过检查,正是这种自由让它危险;但 never 等边界仍有特殊规则。可以说 unknown 是类型安全的顶部类型,never 是底部类型。
还会问 never 在业务代码里有什么用。除了抛错函数,最实用的是穷尽检查。当联合类型新增分支时,never 能让编译器提醒你 switch 没处理完整,这对状态机、消息类型、权限分支很有价值。
工程里外部输入建议统一从 unknown 开始,比如 JSON.parse、第三方 SDK、postMessage、localStorage。先校验再进入业务类型,避免 as User 把脏数据伪装成干净数据。
六、用可赋值关系理解三个类型的位置
把类型看成“可能值的集合”更容易理解:unknown 可以容纳任意输入,但读取能力最少;never 没有任何可能值,所以可以赋给其他类型;any 则是编译器提供的逃生机制,不遵守普通的安全集合关系。尤其要注意,在赋值兼容性中 any 也不能直接赋给 never。
let top: unknown = 42
let text: string = 'ok'
let bottom: never = (() => { throw new Error('stop') })()
top = text // 可以:任何值都能进入 unknown
text = bottom // 可以:never 不会产生实际值
// text = top // 报错:使用 unknown 前必须收窄
| 类型 | 能否接收普通值 | 能否直接访问成员 | 典型用途 |
|---|---|---|---|
any | 能 | 能,但不检查 | 渐进迁移、错误类型的临时隔离 |
unknown | 能 | 不能,需收窄 | 接口响应、消息、异常等边界输入 |
never | 不能 | 不存在可用值 | 穷尽检查、永不返回的控制流 |
假设一个联合类型有 3 个状态,switch 已处理其中 3 个后,剩余分支会收窄为 never;以后新增第 4 个状态,赋值给 never 就会编译失败。这个错误把“新增状态后漏改消费者”从线上风险提前成编译期反馈。
记忆钩子:
unknown是“什么都能进、未经证明不能出”,never是“根本没有值能进”,any是“暂时绕开门禁”。
七、常见误区与追问
- 误区:unknown 和 any 只是名字不同。 两者都能接收任意值,但 unknown 禁止未经收窄的成员访问和具体类型赋值,any 会跳过检查。
- 误区:never 和 void 都表示函数没有返回值。
void表示调用方忽略返回值,函数仍可正常结束;never表示控制流无法正常到达终点。 - 误区:any 可以赋给包括 never 在内的所有类型。
any能绕过大量检查,但不能直接赋给never。 - 追问:unknown 为什么叫顶部类型? 任意类型的值都能安全赋给 unknown,它覆盖所有可能输入,但使用能力必须通过收窄取回。
- 追问:never 为什么能赋给 string? never 没有实际可能值,从空集合到任意类型都不会制造不安全的运行时值。
- 追问:catch 中的 error 应该是什么类型? 开启相关严格选项后应按 unknown 处理,通过
instanceof Error等方式确认后再读 message。 - 追问:如何阻止 any 向核心代码扩散? 在边界立即用 unknown 接住并校验,缩小 any 所在函数范围,同时启用
noImplicitAny和 lint 规则。
八、加强记忆
any 是“我不检查了”,unknown 是“我还不知道,先证明”,never 是“这里不该发生”。安全性排序上,unknown 通常比 any 更值得优先使用。
答题时再补上“边界输入用 unknown、穷尽分支用 never、any 只做受控逃生”,三个类型的工程位置就完整了。