JavaScript 值与类型
本文系统梳理 JavaScript 的数据类型体系与隐式类型转换规则,覆盖工程高频场景:typeof 判断、+ 运算、== 宽松相等、关系比较,以及对象参与运算时的 ToPrimitive 行为,并给出一组经典预测题与工程建议。
1. JavaScript 数据类型全景
1.1 原始类型(Primitive,7 种)
undefinednullbooleannumber(包含NaN、Infinity、-0)stringsymbolbigint
特点:
- 原始值语义上“按值”使用
- 不可变(例如字符串操作会创建新字符串)
1.2 对象类型(Object,1 大类)
object:普通对象、数组、函数、日期、正则、Map/Set 等
特点:
- 变量保存的是引用
- 多个变量可指向同一对象,修改彼此可见
2. 类型检测:typeof / instanceof / 更可靠的方式
2.1 typeof 常见输出与坑
typeof undefined // "undefined"
typeof null // "object"(历史遗留)
typeof 123 // "number"
typeof NaN // "number"
typeof 1n // "bigint"
typeof "x" // "string"
typeof true // "boolean"
typeof Symbol("s") // "symbol"
typeof function() {} // "function"
typeof [] // "object"
typeof {} // "object"
2.2 区分数组/日期等:推荐
Array.isArray([]) // true
Object.prototype.toString.call([]) // "[object Array]"
Object.prototype.toString.call(new Date) // "[object Date]"
2.3 instanceof 的语义
a instanceof B 判断的是:B.prototype 是否出现在 a 的原型链上。它不是通用“类型判断”,跨 iframe/realm 时可能失效。
3. 隐式转换:会在什么场景发生?
常见触发点:
+(可能拼接,也可能加法)- * / % **(一般走数值运算)==(宽松相等)< > <= >=(关系比较)- 条件判断:
if (x)、while (x)、?:(ToBoolean) - 字符串上下文:
`${x}`、"a" + x(ToString/ToPrimitive)
规范抽象操作可归纳为:
- ToBoolean
- ToNumber
- ToString
- ToPrimitive(对象转原始值)
4. ToBoolean:哪些值会被当作 false?
只有以下值是 falsy,其余全部 truthy:
false0、-00n""nullundefinedNaN
Boolean([]) // true
Boolean({}) // true
Boolean("0") // true
Boolean(" ") // true
5. ToNumber:常见输入的数值转换结果
Number("42") // 42
Number("42px") // NaN
Number("") // 0
Number(" ") // 0
Number(null) // 0
Number(undefined) // NaN
Number(true) // 1
Number(false) // 0
Number([]) // 0
Number([1]) // 1
Number([1,2]) // NaN
Number({}) // NaN
Number vs parseInt/parseFloat
Number("42px")→NaNparseInt("42px", 10)→42(从头解析,遇到非法字符停止)
parseInt("08", 10) // 8(建议总是写 radix=10)
6. ToString:常见输入的字符串转换结果
String(123) // "123"
String(null) // "null"
String(undefined) // "undefined"
String([1,2,3]) // "1,2,3"
String({}) // "[object Object]"
String(Symbol("x")) // "Symbol(x)"(显式转换允许)
注意:Symbol 不能被隐式转成字符串:
Symbol("x") + "" // TypeError
7. ToPrimitive:对象如何变成原始值(理解怪异比较的关键)
对象参与 +、==、< 等运算时,会先执行 ToPrimitive:
- 若存在
obj[Symbol.toPrimitive],优先调用(传入 hint) - 否则按 hint 决定顺序(概念化记法):
- hint
"number":先valueOf(),再toString() - hint
"string":先toString(),再valueOf() - hint
"default":多数对象接近"number";Date 更偏"string"
- hint
const d = new Date("2020-01-01")
String(d) // 日期字符串
Number(d) // 时间戳数值(有效日期时)
8. 运算符的隐式转换规则(最高频)
8.1 +:拼接与加法的分水岭
- 若任一侧在转换后走字符串路径,则执行字符串拼接
- 否则执行数值相加
1 + "2" // "12"
"1" + 2 // "12"
1 + 2 + "3" // "33"
"1" + 2 + 3 // "123"
8.2 其他算术运算:通常转数字
"6" * "7" // 42
"10" - 1 // 9
"10" / 2 // 5
"2" ** 3 // 8
9. ==(宽松相等):速查表与结论
工程上建议:优先 === / !==。但读代码、排查问题时必须懂 ==。
9.1 最重要的特例:null / undefined
| 表达式 | 结果 |
|---|---:|
| null == undefined | true |
| null == 0 | false |
| undefined == 0 | false |
结论:null 只在 == 下与 undefined 相等。
9.2 有 boolean:先转数字
true → 1false → 0
false == "0" // true(false->0, "0"->0)
9.3 number vs string:字符串转数字
"0" == 0 // true
"" == 0 // true
" " == 0 // true
9.4 有 object:对象先 ToPrimitive
[] == "" // true([] -> "")
[] == 0 // true([] -> "" -> 0)
[1] == 1 // true([1] -> "1" -> 1)
[1,2] == "1,2" // true([1,2] -> "1,2")
9.5 对象字面量的语法坑
直接写 {} 在表达式位置可能被当作代码块。比较对象字面量请加括号:
({}) == "[object Object]" // true(通常会 ToPrimitive 成该字符串)
10. 关系比较 < > <= >=:字符串字典序 vs 数值比较
- 两边都是字符串:按字典序
- 否则:通常转为数值比较
"2" > "10" // true(字典序)
"2" > 10 // false("2" -> 2)
11. BigInt 与 Symbol:隐式转换的限制
11.1 BigInt 不能与 Number 混合运算
1n + 1 // TypeError
显式统一类型:
1n + 1n // 2n
Number(1n) + 1 // 2
BigInt(1) + 1n // 2n
11.2 Symbol 不能被隐式转成字符串/数字
Symbol("x") + "" // TypeError
String(Symbol("x")) // "Symbol(x)"(显式转换可以)
12. 预测输出:20 道经典题(含最简解析)
解析只给关键转换路径,便于你自行复盘。
0 == ""
true("" -> 0)
0 == "0"
true("0" -> 0)
"" == "0"
false(同为字符串直接比)
false == 0
true(false -> 0)
false == "0"
true(false -> 0,"0" -> 0)
false == ""
true(false -> 0,"" -> 0)
null == undefined
true(特例)
null == 0
false(特例)
undefined == 0
false(特例)
[] == ""
true([] -> "")
[] == 0
true([] -> "" -> 0)
[0] == 0
true([0] -> "0" -> 0)
[1] == 1
true([1] -> "1" -> 1)
[1,2] == "1,2"
true([1,2] -> "1,2")
({}) == "[object Object]"
true(对象 ToPrimitive 后比较)
"2" > "10"
true(字典序)
"2" > 10
false("2" -> 2)
"" + 1 + 2
"12"(先拼接 "1",再拼接 "2")
1 + 2 + ""
"3"(先算数值 3,再拼接)
"5" - "2" + "1"
"31"("5"-"2" -> 3,3 + "1" 拼接)
13. 工程实践建议(直接减少线上坑)
- 默认使用
===/!==,避免==的隐式路径 - 输入数据(表单、URL 参数、localStorage)默认当字符串:需要数值就显式
Number() - 少让“可能是数字也可能是字符串”的变量直接参与
+ - 只想判断
null/undefined时,x == null是一个可读且常见的写法 - 整数解析使用
parseInt(str, 10);需要严格数值校验用Number()+Number.isNaN - BigInt 与 Number 不混算;Symbol 避免隐式拼接