JavaScript / TypeScript 易踩坑知识点完全指南 -假值(Falsy Values)完全指南

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)

记忆口诀:false0空字符串nullundefinedNaN0n

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-03.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 ||

|| 会把所有假值都替换,?? 只替换 nullundefined

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)

黄金法则:需要精确判断时,始终使用严格相等 ===!==,避免隐式类型转换带来的陷阱。

相关推荐
草青工作室1 小时前
java-不携带Token也能“打“自己的接口:一个绕开网关鉴权、用 URL 直接调度 SpringMVC 方法的轻量级定时任务方案
java·开发语言
Coodor1 小时前
信创系统基于QT5操作NFC读写器
开发语言·qt·智能卡·nfc读写器·yw-607hc
古夕1 小时前
my-first-ai-web_学习记录05——NextAuth Adapter 存储用户信息
typescript·全栈·next.js
小鱼咕噜咕噜1 小时前
Element UI的el-upload文件上传与ZIP文件解压上传
javascript
励志不掉头发的内向程序员1 小时前
【LibreCAD 2D架构】从鼠标点击到屏幕像素:LibreCAD绘图架构全链路解析之Action与命令系统
linux·开发语言·c++·qt·学习·系统架构
陈拉斯1 小时前
上线一个多人扫码网页娱乐工具,我在并发、SEO、PWA 和打包脚本上踩的 8 个坑
javascript
民乐团扒谱机1 小时前
【超详细】PyQt6 实现 AU 风格波形图编辑器:多级缩放与采样点逐级渲染,附全过程代码
开发语言·python·编辑器·pyqt·audition·时域·频谱图
s_w.h1 小时前
【 linux 】线程互斥与同步
java·linux·服务器·开发语言
pingglala2 小时前
C++线程安全队列对比
开发语言·c++