一、JS 数据类型
JS 有 7 种基本类型:
numberstringbooleannullundefinedsymbol(ES6)bigint(ES2020)
以及引用类型:object(包括数组、函数、日期等)
⚠️ 注意:
typeof null === 'object'是历史 bug,但null仍是原始类型。
二、隐式转换的三大核心规则
这些规则是 ECMAScript 规范中定义的抽象操作(abstract operation),JS 引擎在执行某些操作时会自动调用它,开发者不能直接调用例如
ToPrimitive(obj, 'string')的操作,除非手动实现。
1. ToPrimitive(input, preferredType?)
将对象转为原始值。
- 如果
preferredType是"string",优先调用toString()→valueOf() - 如果是
"number"(或未指定),优先调用valueOf()→toString()
const obj = {valueOf() { return 42; },toString() { return 'hello'; }
};console.log(obj + 1); // 43 → 先 valueOf()
console.log(String(obj)); // "hello" → 先 toString()
📌 数组、Date 等内置对象也有默认的
valueOf/toString行为。
但它是隐式转换,我们如何知道 preferredType 传递的是谁呢?
其实,在规范的底层早就为某些操作做好了参数传递,只不过这个过程在我们开发者眼中是个“黑盒”。
preferredType 告诉引擎“你希望转成字符串还是数字?”,这个希望我们可以理解为这种规范:
当然,情况比我们想得更复杂,如果只是希望通过面试,掌握最基本的就足够了,希望更深度学习可以看官方文档: https://262.ecma-international.org/#sec-toprimitive
2. ToString(x)
将任意值转为字符串:
| 类型 | 转换结果 |
|---|---|
null |
"null" |
undefined |
"undefined" |
true |
"true" |
42 |
"42" |
[] |
""(空数组 → 空字符串) |
[1,2] |
"1,2" |
{} |
"[object Object]" |
Symbol('id') |
TypeError(不能转字符串) |
'' + 42 // "42"
'' + true // "true"
'' + null // "null"
'' + [1,2] // "1,2"
3. ToNumber(x)
将任意值转为数字:
| 类型 | 转换结果 |
|---|---|
"" |
0 |
"42" |
42 |
"hello" |
NaN |
true |
1 |
false |
0 |
null |
0 |
undefined |
NaN |
[] |
0(先转 "" → 再转 0) |
[1] |
1 |
[1,2] |
NaN("1,2" → 非法数字) |
{} |
NaN |
+"" // 0
+"42" // 42
+true // 1
+[] // 0
+[1,2] // NaN
三、总有面试官喜欢问这些
虽然我作为面试官从来不问这些问题,但有人他就喜欢问,咱能有什么办法。
场景 1:加法运算符 +
- 如果任一操作数是字符串,则另一操作数转为字符串,执行拼接。
- 否则,都转为数字相加。
1 + "2" // "12" (字符串拼接)
1 + 2 // 3
"1" + true // "1true"
[] + [] // "" ([] → "","" + "" → "")
{} + [] // "[object Object]"({} 被视为代码块,浏览器环境实际是 +[] → 0,但 REPL(node.js环境) 中常显示为对象字符串)// 但是如果是赋值的话,a = {} + [] // 只有一个结果 "[object Object]"
[] + {} // "[object Object]"
💡 经典面试题:
[] + {}vs{} + []结果不同,因{}在语句开头被解析为代码块。
场景 2:相等运算符 ==(宽松相等)
== 会进行类型转换(规则见;https://262.ecma-international.org/#sec-abstract-equality-comparison):
常见规则:
null == undefined→truestring == number→ 字符串转数字boolean与任何类型比较 → 先转为number- 对象 vs 原始值 → 对象先
ToPrimitive
42 == "42" // true
true == 1 // true(true → 1)
false == 0 // true
null == undefined // true
[] == 0 // true([] → "" → 0)
![] == [] // true(![] → false,[] → 0,false == 0 → true)
最佳实践:工作中永远使用
===(严格相等),避免隐式转换!
场景 3:逻辑运算符 &&, ||, !
!:先转为布尔值(ToBoolean),再取反&&/||:返回操作数本身(短路求值),但判断依据是“真值性”
多提一嘴短路求值!
在很多语言(如 Java、C++)中,逻辑运算符总是返回
true或false。 但在 JavaScript 中,它们返回的是参与运算的操作数本身(准确来说是决定“真值性” 的那个数)。console.log(42 && "hello"); // "hello" console.log(0 && "hello"); // 0 console.log("" || "default"); // "default" console.log("foo" || "bar"); // "foo"那么,什么叫做决定“真值性”呢?我以上面这几个例子说明:第一个逻辑与,42 是真值,但决定不了整个表达式的逻辑是真还是假,所以能决定“真值性”的是“hello”;第二个同理,0 直接决定了逻辑与就是假,所以可以直接返回;第三个逻辑或,第一个空串为假值,第二个值才能决定整体真假,所以返回“default”;第四个也同理,真假由“foo“就可以决定。
在 JavaScript 中,
falsy 值(转为 false)只有 7 个:
false 0, -0, 0n "", ''(空字符串) null undefined NaN其余所有值都是 truthy(转为 true),包括:
"0", "false", [], {}, [0], function() {}, 42, -1, Infinity, new Date()
我们在公司中经常看到这样的代码:
// 安全调用函数
callback && callback();// 安全访问数组元素
arr && arr.length > 0 && arr[0];
他们就是利用了“短路”求值的规则,保证的代码健壮性。
场景 4:关系运算符 <, >, <=, >=
- 如果双方都是字符串 → 按字典序比较
- 否则 → 都转为数字比较
"2" > "10" // true(字典序:"2" > "1")
2 > "10" // false(2 > 10 → false)
[2] > [10] // false([2]→"2", [10]→"10" → 字符串比较?错!)
// 实际:[2] 和 [10] 都转为数字 → 2 > 10 → false
⚠️ 注意:
[1,2] > [1]→NaN,因为"1,2"转数字为NaN
场景 5:一元运算符 +, -, ++, --
+x:尝试将 x 转为数字(ToNumber)-x:同上,再取负
+"42" // 42
+true // 1
+[] // 0
+[1,2] // NaN
场景 6:[] == ![]
[] == ![] // true// 分解:
![] → false
[] == false → → [] 转原始值:"" → false 转数字:0→ "" 转数字:0→ 0 == 0 → true