【JavaScript】 隐式类型转换
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. 算术运算符:+、-、*、/、%
+运算符(特殊!)- 任一操作数是字符串 → 字符串拼接
- 否则 → 数字加法
// 字符串拼接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]'
- 其他算术运算符(-、*、/、%)
- 强制转为数字,不进行字符串拼接:
'5'-3// 2'5'*'2'// 10'10'/'2'// 5'10'%'3'// 1'abc'-1// NaN'5'-null// 5(null → 0)'5'-undefined// NaN
- 强制转为数字,不进行字符串拼接:
2. 比较运算符
- 宽松相等
==- 类型相同 → 直接比较
- 类型不同 → 转为数字再比较
1=='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:
1==='1'// falsetrue===1// falsenull===undefined// false
- 不进行类型转换,类型不同直接返回 false:
- 关系比较
>、<、>=、<=- 两个操作数都是字符串 → 字典序比较
- 否则 → 转为数字比较
'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. 逻辑运算符
!(逻辑非)- 转为布尔值后取反:
!0// true(0 → false → true)!1// false!''// true!'hello'// false!null// true!undefined// true![]// false([] 是 truthy)!{}// false({} 是 truthy)
- 转为布尔值后取反:
&&和||- 返回操作数本身,不是布尔值:
// &&:如果第一个是 falsy,返回第一个,否则返回第二个0&&'hello'// 01&&'hello'// 'hello'null&&5// null// ||:如果第一个是 truthy,返回第一个,否则返回第二个0||'hello'// 'hello'1||'hello'// 1null||5// 5
- 返回操作数本身,不是布尔值:
4. 条件语句(if、while、for、三元运算符)
条件表达式会转为布尔值:
if(0){}// false,不执行if(''){}// false,不执行if(null){}// false,不执行if([]){}// true,执行([] 是 truthy)if({}){}// true,执行({} 是 truthy)constresult=0?'yes':'no'// 'no'5. 一元运算符
+''// 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()
constobj={valueOf(){return10;},toString(){return'hello';}};console.log(obj+5)// 15(使用 valueOf)console.log(String(obj))// 'hello'(使用 toString,hint 是 string)console.log(+obj)// 10(使用 valueOf,hint 是 number)
数组的特殊行为:
[]+[]// ''(两个数组都转空字符串)[]+{}// '[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
constobj={[Symbol.toPrimitive](hint){if(hint==='number')return10;if(hint==='string')return'hello';returnnull;}};console.log(obj+5)// 15(number hint)console.log(`${obj}`)// 'hello'(string hint)console.log(obj+'')// 'null'(default hint)四、特殊陷阱和难点
+运算符的歧义// 一元 + 和二元 + 不同+[]// 0(一元 + 转数字)[]+[]// ''(二元 + 字符串拼接)// 注意区分1+2+'3'// '33'(从左到右:3 + '3' → '33')'1'+2+3// '123'(从左到右:'12' + 3 → '123')null和undefined的特殊性null==undefined// true(特殊规则)null===undefined// falsenull==0// false(null 只与 undefined 相等)undefined==0// falseNumber(null)// 0Number(undefined)// NaN- 数组比较的陷阱
[]==![]// true(![] → !Boolean([]) → !true → false→0;数组转原始值:[] → '' → Number('') → 0;0==0)[]==[]// false(引用比较,不是同一个对象)[1]==[1]// false(引用比较)[1]==1// true([1] → '1' → 1) - 布尔值比较的陷阱
if(' '){}// true(空格字符串是 truthy)if('false'){}// true(字符串 'false' 是 truthy)if(0){}// falseif(newBoolean(false)){}// true(对象总是 truthy) - Symbol 的特殊性
constsym=Symbol('id');sym+1// TypeError(不能隐式转换 Symbol)String(sym)// 'Symbol(id)'(可以显式转换)
五、如何避免隐式转换的坑
- 使用严格相等
===和!==// 避免if(value==0){}// 推荐if(value===0){}if(value===null||value===undefined){}2.显式类型转换 javascript// 不推荐'5'-0// 5// 推荐Number('5')// 5parseInt('5',10)// 5+'5'// 5(一元 + 是显式转数字)// 不推荐5+''// '5'// 推荐String(5)// '5'`${5}`// '5' - 使用
Number.isNaN()检测 NaNisNaN('abc')// true(先转数字)Number.isNaN('abc')// false(不转换,直接判断)Number.isNaN(NaN)// true - 使用
Object.is()替代 ===NaN==NaN// falseNaN===NaN// falseObject.is(NaN,NaN)// true+0===-0// trueObject.is(+0,-0)// false
六、常见面试题汇总
console.log([]==![])// true (![] → !Boolean([]) → !true → false→0;数组转原始值:[] → '' → Number('') → 0;0==0)console.log([]==0)// trueconsole.log([]=='')// trueconsole.log({}==0)// false({} → '[object Object]' → NaN)console.log({}=='[object Object]')// trueconsole.log('5'-3)// 2console.log('5'+3)// '53'console.log('5'-'3')// 2console.log('5'+'3')// '53'console.log(true+false)// 1console.log(true+true)// 2console.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. 输出什么?consta={valueOf(){return1;},toString(){return'2';}};console.log(a+1)// 2(valueOf 优先)console.log(String(a))// '2'(toString 优先)