先记住这个答案
unknown 和 any 都可以接收许多不同类型的值,但使用规则差别很大。any 会让相关操作跳过大量静态检查,属性读取、调用和赋给具体类型都可能直接通过;unknown 要求先通过检查或其他证明缩小范围,才能执行依赖具体类型的操作。外部数据、未知异常和通用边界通常适合从 unknown 开始,再解析为可信结构。unknown 本身不验证数据,类型断言也不执行验证,真正的安全来自与目标契约匹配的运行时检查。
- any 容易把未检查的信息传播到后续代码
- unknown 保留使用前先证明类型的责任
- 把数据标为 unknown 只是校验流程的起点
同样未知,使用时为何差别很大
函数接收 any 后调用 trim,即使输入是数字,编译器通常也不会阻止;错误会留到运行时。换成 unknown,调用位置必须先证明输入是字符串。这样未知信息被留在边界,而不会因为一次随意操作扩散成更多看似可信的值。
示例把检查和格式化放在同一小函数里,字符串可以继续使用方法,其他输入则走明确失败路径。预期错误注释演示未经检查的调用不能通过,实际业务不应保留那段错误操作,而应只暴露已检查的实现。
function unsafe(value: any) {
return value.trim();
}
function rejected(value: unknown) {
// @ts-expect-error unknown 尚未证明具有 trim
return value.trim();
}
function readLabel(value: unknown): string {
if (typeof value !== 'string') throw new TypeError('label must be a string');
return value.trim();
}unsafe 的类型检查不能保证 trim 存在,rejected 明确展示 unknown 阻止同一操作。readLabel 通过真实判断建立字符串证据,传入非字符串时会主动抛出可诊断错误。
外部 JSON 如何避免继续传播 any
某些解析接口返回 any。可以在接收位置把结果保留为 unknown,再交给解析函数,防止业务代码直接读取深层字段。解析函数需要验证当前功能实际依赖的结构、数组元素以及格式限制,不能只检查最外层是对象就宣布整个响应可信。
若服务端接口有生成类型,也仍要区分编译时契约与实际网络响应。部署不同步、缓存旧结构或错误响应都可能打破静态假设。可以根据边界可靠性选择校验范围,但应明确由哪一层承担验证责任,而不是让任意断言散落在组件中。
什么时候可能保留 any
迁移旧库或兼容极为动态的调用协议时,局部 any 有时是明确的工程选择。应尽量限制在适配器内部,通过小范围封装暴露稳定返回类型,并为关键行为提供运行验证。把整个项目统一改成 any 会失去编译器发现不匹配的价值。
双重断言经过 unknown 再转成目标类型,并没有补充任何证据,只是绕过部分可赋值性限制。如果确实需要断言,应能说明证据来自哪个受控约束;无法说明时,优先设计解析或守卫函数,而不是寻找让报错消失的写法。
容易答错的地方
- unknown 会自动检查网络响应
- unknown 只限制静态使用方式,不会遍历字段或拒绝非法值;必须执行真实解析逻辑,之后才能把输入作为业务类型使用。
- any 只影响当前这一行代码
- 从 any 读取或计算得到的值可能继续携带宽松类型,后续赋值也可能失去保护,因此应在边界尽早隔离并明确返回契约。
面试官还会怎么问?
unknown 能直接赋给 string 吗?
未经收窄通常不能,因为输入也可能是数字或对象;通过 typeof 等检查证明为字符串后,相应分支内才可以按字符串使用。
给 JSON.parse 加类型断言足够吗?
断言没有运行时验证效果。它只适合已有可靠外部保证的场景;面对不可信或可能变化的响应,应使用实际解析逻辑并处理失败。
捕获异常后为什么经常需要先判断类型?
JavaScript 可以抛出字符串、对象甚至空值,未知异常不保证具有 message 字段;应按 Error 实例或其他可识别结构分别处理。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。