对象转原始类型是根据什么流程运行的?
面试速答(30 秒版 TL;DR)
- 对象参与
+、==、<等运算时,都会先走 ToPrimitive(obj, hint)。 - 顺序(必须背):
- 先看
obj[Symbol.toPrimitive],有就调用它(传入 hint:"number"|"string"|"default")。 - 否则按 hint 选择顺序:
- hint 是
"string":先toString()后valueOf() - hint 是
"number"或"default":先valueOf()后toString()
- hint 是
- 先看
Date很特殊:它把"default"当成"string"处理,所以new Date() + 1往往走字符串拼接。
Mermaid:一眼看懂 ToPrimitive 流程
为什么会有 hint?
不同语境期望的“默认语义”不同:
String(obj)倾向拿“可读字符串”,所以 hint 通常是"string"。- 数值运算(如
obj - 1)倾向拿“数值”,hint 通常是"number"。 +和==等有历史兼容行为,常会用"default"。
关键例子(面试最常出)
例 1:[] 为什么经常表现得像空字符串?
String([]); // ""
Number([]); // 0
[] + 1; // "1"
解释口径:
[]是对象,先 ToPrimitive。- 数组的
toString()结果是""(空数组)或逗号分隔的元素字符串。 - 再根据语境 ToNumber/ToString。
例 2:{} + 1 为什么很坑?
这题受“语法位置”和解析方式影响(块语句 vs 对象字面量),面试更建议你答:
- “对象参与
+会先 ToPrimitive,但{} + 1这个表达式本身有解析歧义,业务代码不要写这种。”
如何自定义对象的“转换结果”?
优先级从高到低:
Symbol.toPrimitivevalueOftoString
示例:
const a = {
i: 1,
[Symbol.toPrimitive](hint) {
if (hint === "string") return `i=${this.i}`;
return this.i++;
},
};
String(a); // "i=1"
a + 1; // 2 (a 先变 1,再加 1)
a + 1; // 3
易错点
- ToPrimitive 要求返回“原始值”(primitive)。如果
Symbol.toPrimitive/valueOf/toString返回的还是对象,规范会继续尝试或最终抛TypeError。 Date的默认 hint 行为和普通对象不一样,别把“普通对象的默认顺序”硬套到 Date 上。