JavaScript / TypeScript 易踩坑知识点完全指南
本文档整合了 JS/TS 中所有常见的"看起来没问题但实际会踩坑"的知识点,包括假值、隐式类型转换、NaN、typeof、引用比较、逻辑运算符短路、空值合并与可选链等。
一、假值(Falsy Values)
1.1 什么是假值
在 JavaScript(以及 TypeScript)中,当一个值被放在布尔上下文中使用时(如 if 条件、三元运算符 ? :、&&、|| 等),会被自动转换为布尔值。其中会被转换为 false 的值称为假值(Falsy Values)。
核心规则:JS/TS 中只有 7 个假值,其余全部都是真值(Truthy)。
1.2 7 个假值一览
| 假值 | 类型 | 说明 |
|---|---|---|
false |
Boolean | 布尔假 |
0 |
Number | 数字零(包括 -0) |
0n |
BigInt | BigInt 类型的零 |
"" / '' / `` |
String | 空字符串 |
null |
Object | 空对象引用 |
undefined |
Undefined | 未定义 |
NaN |
Number | 非数字(Not a Number) |
记忆口诀:false、0、空字符串、null、undefined、NaN、0n
1.3 常见踩坑场景
场景 1:后端返回 0 做状态判断
javascript
// 后端返回 isShelf = 0 表示上架
const status = row.isShelf ? '上架' : '下架'
// ❌ 错误!isShelf 为 0 时走进 false 分支,显示"下架",与实际语义相反
// ✅ 正确写法:显式比较
const status = row.isShelf === 1 ? '上架' : '下架'
场景 2:空字符串被误判
javascript
const name = ''
if (name) {
// 这段代码不会执行,因为 '' 是假值
}
场景 3:0 作为有效数据被过滤掉
javascript
const scores = [10, 0, 5, 0, 8]
const validScores = scores.filter(s => s)
// ❌ [10, 5, 8] --- 0 被意外过滤掉了!
// ✅ 正确写法
const validScores = scores.filter(s => s !== undefined && s !== null)
场景 4:NaN 的特殊性
javascript
NaN === NaN // false!NaN 不等于任何值,包括它自己
Number('abc') // NaN
// ✅ 判断 NaN 只能用 isNaN 或 Number.isNaN
isNaN(NaN) // true
Number.isNaN(NaN) // true
场景 5:空对象和空数组是真值
javascript
if ({}) console.log('空对象是真值') // ✅ 会输出
if ([]) console.log('空数组是真值') // ✅ 会输出
if (new Boolean(false)) console.log('Boolean对象也是真值') // ✅ 会输出
// 这些都不是假值!因为它们是对象类型
1.4 真值(Truthy)速查
除了上面 7 个假值之外的所有值都是真值,包括但不限于:
| 真值示例 | 类型 |
|---|---|
true |
Boolean |
42、-0、3.14 |
Number(非零数字) |
"0"、"false"、" " |
String(非空字符串,哪怕只含空格) |
"0" 是假值吗? |
不是!"0" 是非空字符串,是真值 |
[] |
Array(空数组) |
{} |
Object(空对象) |
function(){} |
Function |
new Date() |
Date 对象 |
/regex/ |
RegExp |
二、隐式类型转换(== vs ===)
== 会做隐式类型转换,=== 不会。这是最常见的坑:
javascript
// ❌ == 会隐式转换
0 == '' // true
0 == false // true
'' == false // true
null == undefined // true
'1' == 1 // true
// ✅ === 严格比较,不做转换
0 === '' // false
0 === false // false
null === undefined // false
规则:永远用 ===,不用 ==。
三、null vs undefined
两者都是"空",但含义不同:
null |
undefined |
|
|---|---|---|
| 含义 | 主动赋的"空值" | 变量声明了但没赋值 |
| typeof | 'object'(历史 bug) |
'undefined' |
| 典型场景 | API 返回"无数据" | 变量未初始化 |
javascript
let a; // a 是 undefined
let b = null; // b 是 null
typeof a // 'undefined'
typeof b // 'object'(⚠️ 历史遗留 bug)
a === b // false
a == b // true(⚠️ == 会把它们视为相等)
最佳实践:
javascript
// 判断 null 和 undefined 一起
if (value != null) { ... } // 同时排除 null 和 undefined
// 只判断 undefined
if (value === undefined) { ... }
// 只判断 null
if (value === null) { ... }
四、NaN(Not a Number)
NaN 是"不是一个有效数字",但它本身是 number 类型,且不等于自身:
javascript
typeof NaN // 'number'(⚠️)
NaN === NaN // false(⚠️)
// ❌ 错误判断方式
if (value === NaN) { ... } // 永远不会成立
// ✅ 正确判断方式
Number.isNaN(value) // 推荐
isNaN(value) // 不推荐,会先做类型转换
五、typeof 的坑
typeof 并不总是返回你期望的类型:
javascript
typeof null // 'object'(⚠️ 历史 bug)
typeof undefined // 'undefined'
typeof NaN // 'number'(⚠️)
typeof [] // 'object'(⚠️ 数组也是 object)
typeof function(){} // 'function'(✅ 唯一特殊的)
// ✅ 正确判断数组
Array.isArray([]) // true
六、引用类型的比较
对象和数组比较的是引用地址,不是内容:
javascript
const a = { x: 1 }
const b = { x: 1 }
a === b // false(⚠️ 地址不同)
const c = a
a === c // true(同一个引用)
const arr1 = [1, 2]
const arr2 = [1, 2]
arr1 === arr2 // false(⚠️)
深比较的替代方案:
javascript
// 浅比较
JSON.stringify(a) === JSON.stringify(b) // 可行但有局限(函数、undefined 会丢失)
// 使用 lodash
_.isEqual(a, b) // 深比较
七、逻辑运算符的"短路"行为
&& 和 || 返回的不是布尔值,而是操作数本身:
javascript
// || 返回第一个真值
const name = '' || '默认值' // '默认值'
const name2 = '张三' || '默认值' // '张三'
// && 返回第一个假值或最后一个真值
const result = true && 'hello' // 'hello'
const result2 = false && 'hello' // false
const result3 = 0 && 'hello' // 0
// ⚠️ 这就是为什么 row.isShelf && '下架' 在 isShelf=0 时返回 0 而不是 '下架'
八、空值合并运算符 ?? vs ||
|| 会把所有假值都替换,?? 只替换 null 和 undefined:
javascript
// || 会误伤 0、''、false
0 || '默认值' // '默认值'(⚠️ 0 被误伤了)
'' || '默认值' // '默认值'(⚠️ 空字符串也被误伤)
false || '默认值' // '默认值'(⚠️)
// ?? 只处理 null 和 undefined
0 ?? '默认值' // 0(✅ 保留了 0)
'' ?? '默认值' // ''(✅ 保留了空字符串)
null ?? '默认值' // '默认值'
undefined ?? '默认值' // '默认值'
规则:当默认值可能遇到 0、''、false 时,用 ?? 而不是 ||。
九、可选链 ?.
安全访问深层属性,避免 Cannot read property of undefined:
javascript
const user = null
// ❌ 直接访问会报错
user.address.city // TypeError
// ✅ 可选链,遇 null/undefined 返回 undefined
user?.address?.city // undefined(不报错)
// 配合 ?? 使用
const city = user?.address?.city ?? '未知' // '未知'
十、最佳实践总结
10.1 状态值判断
javascript
// ❌ 危险:后端返回 0 时行为不符合预期
const label = row.isShelf ? '上架' : '下架'
// ✅ 安全:显式比较
const label = row.isShelf === 1 ? '上架' : '下架'
10.2 判断"有值"时明确排除范围
javascript
// ❌ 不推荐:0 和空字符串会被误判为"无值"
if (value) { ... }
// ✅ 推荐:明确排除 null 和 undefined
if (value != null) { ... } // 同时排除 null 和 undefined
// ✅ 推荐:排除 null、undefined 和空字符串
if (value != null && value !== '') { ... }
10.3 安全访问工具函数
typescript
/** 判断值是否有效(非 null、非 undefined、非空字符串) */
export function isValid<T>(value: T | null | undefined | ''): value is T {
return value != null && value !== ''
}
/** 判断值是否为假值 */
export function isFalsy(value: unknown): boolean {
return !value && value !== 0 && value !== false
}
/** 安全获取值(提供默认值) */
export function safeGet<T>(value: T | null | undefined, fallback: T): T {
if (value == null || value === '') return fallback
return value
}
10.4 TypeScript 中的类型守卫
typescript
function processValue(value: number | null | undefined) {
// TypeScript 能正确收窄类型
if (value !== null && value !== undefined) {
// 这里 value 的类型被收窄为 number
console.log(value.toFixed(2)) // ✅ 不会报错
}
}
十一、总结对照表
| 坑点 | 错误写法 | 正确写法 |
|---|---|---|
| 相等比较 | a == b |
a === b |
| 判断 NaN | val === NaN |
Number.isNaN(val) |
| 判断数组 | typeof arr === 'array' |
Array.isArray(arr) |
| 判断 null | typeof val === 'null' |
val === null |
| 默认值(可能为0) | `val | |
| 深层属性访问 | a.b.c |
a?.b?.c |
| 数字状态码判断 | val ? 'A' : 'B' |
val === 1 ? 'A' : 'B' |
| 对象/数组比较 | a === b |
JSON.stringify(a) === JSON.stringify(b) 或 _.isEqual(a, b) |
黄金法则:需要精确判断时,始终使用严格相等
===和!==,避免隐式类型转换带来的陷阱。