JavaScript类型转换分隐式和显式两条线,隐式发生在==、+、!、if等场景,触发ToPrimitive、ToNumber、ToBoolean等抽象操作,规则分散易踩坑;===不转换,生产环境应禁用==。
JavaScript 的类型转换不是一套固定公式,而是分「隐式」和「显式」两条线运行,且隐式转换规则分散在不同操作符和上下文中,不系统梳理就容易踩坑。

隐式转换发生在 JavaScript 强制要求操作数为某类型却得到其他类型时,比如 ==、+、!、if 条件判断、while 循环条件等。它不发生在 === 或函数参数传入时(参数本身不转,但后续运算可能触发)。
== 会先调用 ToPrimitive,再按规则比对;=== 完全跳过转换+ 遇到字符串,优先转字符串拼接;其他情况才尝试转数字!x 先执行 ToBoolean,再取反;if (x) 只走 ToBoolean
document.getElementById() 返回 null,但 if (el) 判断时是靠 ToBoolean(null) === false
ToNumber 和 Number() 的行为一致吗?基本一致,但有细微差别:独立调用 Number(x) 是显式转换,严格按规范执行;而隐式转换中的 ToNumber 在部分边界场景下与之等价,但你无法直接调用 ToNumber —— 它是规范内部抽象操作。
Number(" 123 ") → 123;Number("123abc") → NaN
+"123"(一元加号)等价于 Number("123"),但 +" 123 " 同样返回 123
Number({}) → NaN;+[{}] → 0(因为 [{}].toString() === "[object Object]",再 ToNumber 得 NaN?错 —— 实际上 [{}] 先被 ToPrimitive 转成 "[object Object]",再 ToNumber 得 NaN,但等等:这里有个关键点——+ 对数组的处理是先 toString 再 ToNumber,而 [{}].toString() 是 "[object Object]",所以 +[{}] 实际是 NaN;但 +[] 是 0,因为 [].toString() === "",ToNumber("") === 0
Boolean() 和 !!x 真的完全等价?语义上等价,都是执行 ToBoolean 抽象操作,但写法意图不同:Boolean(x) 明确表达“我要一个布尔值”,!!x 更常用于压缩或快速取布尔态,可读性略低。
false:false、0、-0、0n(BigInt 零)、""、null、undefined、NaN
new Boolean(false) 是对象,ToBoolean(new Boolean(false)) === true —— 这是典型陷阱:包装对象永远为真!!"0" → true(非空字符串为真),有人误以为字符串 "0" 是假值[] == ![] 返回 true?这不是 bug,是 == 规则叠加作用的结果:左边 [] 被 ToPrimitive 转为空字符串 "";右边 ![] 先转布尔([] 为真 → ![] 为 false),再 == 比较时把 false 转数字得 0,最后比较 "" == 0 → 都转数字,"" → 0,所以 0 == 0。
[] == ![] → [] == false → "" == false → 0 == 0 → true
{} == !{} 在非严格模式下会报语法错误(因 {} 被解析为代码块),但 ({}) == !({}) 中,({}) 是对象字面量,!({}) → false,({}) == false → ToPrimitive({}) → "[object Object]" == false → "[object Object]" == 0 → NaN == 0 → false
==,一律用 ===
真正难的不是记住每条规则,而是意识到「JavaScript 类型转换从不孤立发生」——它总嵌套在操作符优先级、对象方法调用链、甚至引擎优化路径中。比如 obj + "" 触发 obj.toString(),但若重写了 valueOf() 且返回原始值,就可能走另一条 ToPrimitive 分支。这种动态性才是日常调试中最容易漏掉的一环。