JavaScript 是弱类型语言,在运算过程中会自动进行类型转换,这就是隐式类型转换(Implicit Coercion)。
一、类型转换的三大核心规则
JavaScript 的隐式转换主要围绕三种类型进行:字符串(String) 、数字(Number) 、布尔值(Boolean)。
1. 转换为字符串(ToString)
| 原始值 | 转换结果 |
|---|---|
| undefined | 'undefined' |
| null | 'null' |
| true | 'true' |
| false | 'false' |
| 0 | '0' |
| NaN | 'NaN' |
| 对象 | 调用 toString() 或 valueOf() |
2. 转换为数字(ToNumber)
| 原始值 | 转换结果 |
|---|---|
| undefined | NaN |
| null | 0 |
| true | 1 |
| false | 0 |
| ''(空字符串) | 0 |
| '123'(数字字符串) | 123 |
| 'abc'(非数字字符串) | NaN |
| 对象 | 调用 valueOf() 或 toString() |
3. 转换为布尔值(ToBoolean)
假值(Falsy)(转换为 false):
-
undefined
-
null
-
false
-
0、-0、0n
-
NaN
-
''(空字符串)
其他所有值都是真值(Truthy),包括:
-
' '(空格字符串)
-
'false'(字符串)
-
\[\](空数组)
-
{}(空对象)
二、常见触发隐式转换的场景
1. 算术运算符:+、-、*、/、%
-
+运算符(特殊!)-
任一操作数是字符串 → 字符串拼接
-
否则 → 数字加法
javascript// 字符串拼接 1 + '2' // '12' 'hello' + 1 // 'hello1' true + 'abc' // 'trueabc' // 数字加法 1 + true // 2(true → 1) 1 + null // 1(null → 0) 1 + undefined // NaN(undefined → NaN) 1 + [] // '1'([] → '',然后拼接) 1 + {} // '1[object Object]'
-
-
其他算术运算符(-、*、/、%)
-
强制转为数字,不进行字符串拼接:
javascript'5' - 3 // 2 '5' * '2' // 10 '10' / '2' // 5 '10' % '3' // 1 'abc' - 1 // NaN '5' - null // 5(null → 0) '5' - undefined // NaN
-
2. 比较运算符
-
宽松相等
==-
类型相同 → 直接比较
-
类型不同 → 转为数字再比较
javascript1 == '1' // true('1' → 1) true == 1 // true(true → 1) false == 0 // true(false → 0) null == undefined // true(特殊规则) null == 0 // false(null 只与 undefined 相等) NaN == NaN // false(NaN 不等于任何值) '1' == true // true(都转数字:1 == 1) 'abc' == true // false('abc' → NaN,NaN != 1) [] == '' // true([] → '') [] == 0 // true([] → '' → 0) [1] == 1 // true([1] → '1' → 1) [1,2] == '1,2' // true(数组转字符串) {} == '[object Object]' // true
-
-
严格相等
===-
不进行类型转换,类型不同直接返回 false:
javascript1 === '1' // false true === 1 // false null === undefined // false
-
-
关系比较
>、<、>=、<=-
两个操作数都是字符串 → 字典序比较
-
否则 → 转为数字比较
javascript'2' > '10' // true(字符串比较:'2' > '10') 2 > '10' // false('10' → 10) '2' > 10 // false('2' → 2) 'abc' > 1 // false('abc' → NaN,任何比较都是 false) null > 0 // false(null → 0) null >= 0 // true(null → 0)
-
3. 逻辑运算符
-
!(逻辑非)-
转为布尔值后取反:
javascript!0 // true(0 → false → true) !1 // false !'' // true !'hello' // false !null // true !undefined // true ![] // false([] 是 truthy) !{} // false({} 是 truthy)
-
-
&&和||-
返回操作数本身,不是布尔值:
javascript// &&:如果第一个是 falsy,返回第一个,否则返回第二个 0 && 'hello' // 0 1 && 'hello' // 'hello' null && 5 // null // ||:如果第一个是 truthy,返回第一个,否则返回第二个 0 || 'hello' // 'hello' 1 || 'hello' // 1 null || 5 // 5
-
4. 条件语句(if、while、for、三元运算符)
条件表达式会转为布尔值:
javascript
if (0) {} // false,不执行
if ('') {} // false,不执行
if (null) {} // false,不执行
if ([]) {} // true,执行([] 是 truthy)
if ({}) {} // true,执行({} 是 truthy)
const result = 0 ? 'yes' : 'no' // 'no'
5. 一元运算符
javascript
+'' // 0
+'123' // 123
+'abc' // NaN
+true // 1
+false // 0
+null // 0
+undefined // NaN
+[] // 0([] → '' → 0)
+{} // NaN({} → '[object Object]' → NaN)
-'' // -0
-'123' // -123
-'abc' // NaN
三、对象/数组的转换:ToPrimitive
当对象(包括数组、函数等)参与运算时,会先执行 ToPrimitive 操作,将其转换为原始值。
转换流程:
-
优先调用 Symbol.toPrimitive(如果存在)
-
否则根据上下文提示(hint)调用 valueOf() 或 toString()
javascriptconst obj = { valueOf() { return 10; }, toString() { return 'hello'; } }; console.log(obj + 5) // 15(使用 valueOf) console.log(String(obj)) // 'hello'(使用 toString,hint 是 string) console.log(+obj) // 10(使用 valueOf,hint 是 number)
数组的特殊行为:
javascript
[] + [] // ''(两个数组都转空字符串)
[] + {} // '[object Object]'
{} + [] // 0(在浏览器中,{} 被解析为代码块,相当于 +[])
[1,2] + [3,4] // '1,23,4'(数组转字符串:'1,2' + '3,4')
[1] + 1 // '11'([1] → '1' → '1' + 1)
[1] - 1 // 0([1] → '1' → 1 - 1)
自定义 ToPrimitive
javascript
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') return 10;
if (hint === 'string') return 'hello';
return null;
}
};
console.log(obj + 5) // 15(number hint)
console.log(`${obj}`) // 'hello'(string hint)
console.log(obj + '') // 'null'(default hint)
四、特殊陷阱和难点
-
+运算符的歧义javascript// 一元 + 和二元 + 不同 +[] // 0(一元 + 转数字) [] + [] // ''(二元 + 字符串拼接) // 注意区分 1 + 2 + '3' // '33'(从左到右:3 + '3' → '33') '1' + 2 + 3 // '123'(从左到右:'12' + 3 → '123') -
null和undefined的特殊性javascriptnull == undefined // true(特殊规则) null === undefined // false null == 0 // false(null 只与 undefined 相等) undefined == 0 // false Number(null) // 0 Number(undefined) // NaN -
数组比较的陷阱
javascript[] == ![] // true(![] → !Boolean([]) → !true → false→0;数组转原始值:[] → '' → Number('') → 0;0==0) [] == [] // false(引用比较,不是同一个对象) [1] == [1] // false(引用比较) [1] == 1 // true([1] → '1' → 1) -
布尔值比较的陷阱
javascriptif (' ') {} // true(空格字符串是 truthy) if ('false') {} // true(字符串 'false' 是 truthy) if (0) {} // false if (new Boolean(false)) {} // true(对象总是 truthy) -
Symbol 的特殊性
javascriptconst sym = Symbol('id'); sym + 1 // TypeError(不能隐式转换 Symbol) String(sym) // 'Symbol(id)'(可以显式转换)
五、如何避免隐式转换的坑
-
使用严格相等
===和!==javascript// 避免 if (value == 0) {} // 推荐 if (value === 0) {} if (value === null || value === undefined) {} 2. 显式类型转换 javascript // 不推荐 '5' - 0 // 5 // 推荐 Number('5') // 5 parseInt('5', 10) // 5 + '5' // 5(一元 + 是显式转数字) // 不推荐 5 + '' // '5' // 推荐 String(5) // '5' `${5}` // '5' -
使用
Number.isNaN()检测 NaNjavascriptisNaN('abc') // true(先转数字) Number.isNaN('abc') // false(不转换,直接判断) Number.isNaN(NaN) // true -
使用
Object.is()替代 ===javascriptNaN == NaN // false NaN === NaN // false Object.is(NaN, NaN) // true +0 === -0 // true Object.is(+0, -0) // false
六、常见面试题汇总
javascript
console.log([] == ![]) // true (![] → !Boolean([]) → !true → false→0;数组转原始值:[] → '' → Number('') → 0;0==0)
console.log([] == 0) // true
console.log([] == '') // true
console.log({} == 0) // false({} → '[object Object]' → NaN)
console.log({} == '[object Object]') // true
console.log('5' - 3) // 2
console.log('5' + 3) // '53'
console.log('5' - '3') // 2
console.log('5' + '3') // '53'
console.log(true + false) // 1
console.log(true + true) // 2
console.log([] + []) // ''
console.log([] + {}) // '[object Object]'
console.log({} + []) // 0(浏览器)或 '[object Object]'
// 4. 输出什么?
console.log(1 < 2 < 3) // true(1 < 2 → true → 1 < 3 → true)
console.log(3 > 2 > 1) // false(3 > 2 → true → 1 > 1 → false)
// 5. 输出什么?
const a = {
valueOf() { return 1; },
toString() { return '2'; }
};
console.log(a + 1) // 2(valueOf 优先)
console.log(String(a)) // '2'(toString 优先)