先记住这个答案
当两个联合类型的值通过 === 判断相等,TypeScript 可以保留两者共有的可能类型,例如 string | number 与 string | boolean 相等时,成功分支可收窄到 string。与具体字面量比较也能排除其他候选,switch 利用同类标签信息。value != null 是一个常见的特定用法,按 JavaScript 规则同时排除 null 和 undefined;value !== null 则只排除 null。这个空值习惯不代表所有非严格相等都具有同样清楚的类型关系,其他隐式转换比较仍应谨慎。
- 严格相等可以建立双方共同候选的证据
- != null 同时排除 null 和 undefined
- !== null 不会顺带排除 undefined
严格相等为什么能缩小双方范围
一个值可能是文本或数字,另一个可能是文本或布尔值。严格相等不会把数字转换为字符串,也不会把布尔值变成数字,因此成功时共同候选只剩文本。分支内可以对两边使用字符串方法,而失败分支并不能简单断言两边都不是字符串。
示例把这种共同候选与空值检查放在一起展示。空值函数保留零,避免真值判断的误伤。另一个函数只排除 null,故 undefined 仍然存在,不能立即执行数字格式化;这里的预期错误帮助区分两个看似相近的操作符。
function compare(a: string | number, b: string | boolean) {
if (a === b) return a.toUpperCase() + b.toLowerCase();
return 'different';
}
function format(value: number | null | undefined) {
if (value != null) return value.toFixed(1);
return 'missing';
}
function onlyNull(value: number | null | undefined) {
if (value !== null) {
// @ts-expect-error undefined 仍没有被排除
return value.toFixed(1);
}
}严格相等的成功分支保留双方的字符串能力,非严格空值比较同时移除两种空值。只排除 null 的反例应报可能为 undefined,而不是被误认为已经完成判空。
失败分支没有成功分支那么强的结论
两个字符串内容不同也会让相等判断失败,因此 a !== b 不能证明它们一定属于不同基础类型。收窄利用的是能够从运行时关系可靠推出的事实,而不是把成功分支的结论机械取反到每个变量上。阅读复杂条件时要考虑完整候选组合。
对象的严格相等比较主要检查引用身份,不会逐字段比较结构。两个独立创建但内容相同的对象通常不相等;接口结构兼容也不会使它们在运行时自动成为同一个对象。需要按业务标识匹配时,应比较明确字段并处理字段有效性。
switch 与空值风格如何选择
有限字面量状态用 switch 便于组织分支,并可结合 never 检查遗漏。只处理单个空值边界时,显式比较往往更简洁。团队若禁止非严格相等,可以写 value !== null && value !== undefined,保持相同的空值排除意图。
不要把 != null 的特殊便利推广到数字字符串等其他宽松比较。隐式转换可能让结果超出阅读者预期,也不一定提供想要的静态关系。涉及用户输入的数值转换,应显式解析并校验结果,使转换、失败处理和类型证据都可见。
容易答错的地方
- !== null 可以当作同时检查未定义
- 它只排除 null,undefined 仍可能到达后续代码;如果两种都表示缺失,应分别比较或按团队规则使用非严格空值比较。
- 相等失败就说明双方类型不同
- 相同类型的两个不同值也会比较失败,尤其是字符串和对象引用;应根据完整值关系推导,不能只把成功分支的类型结论反转。
面试官还会怎么问?
为什么不直接用 if(value) 排除空值?
真值检查还会过滤零、空串和 false 等值;只有它们都应该被排除时才符合业务语义,否则明确空值比较更加准确。
switch 的 case 能改变原来的值吗?
分支匹配只选择执行路径并提供类型信息,不会转换原值;若 case 中有实际赋值,那是另一项运行时操作,需要单独分析。
NaN 能用等于自身来识别吗?
NaN 的相等行为比较特殊,业务检查通常使用 Number.isNaN 更清楚;还要区分只拒绝 NaN 与要求有限数字这两种验证目标。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。