JavaScript隐式类型转换深度解析—ToPrimitive与抽象相等算法|新宇宙博客typeof
true
typeof
42
typeof
'hello'
typeof
Symbol
typeof
42n
typeof
typeof
typeof
function
2. ToPrimitive 抽象操作
const obj = {
[Symbol.toPrimitive](hint) {
console.log('hint:', hint);
switch (hint) {
case 'number': return 42;
case 'string': return 'hello';
default: return 'default';
}
}
};
console.log(+obj);
console.log(`${obj}`);
console.log(obj + '');
console.log(obj == 42);
valueOf 和 toString 的调用顺序
const complex = {
valueOf() {
console.log('valueOf called');
return 100;
},
toString() {
console.log('toString called');
return 'complex object';
}
};
console.log(complex + 1);
console.log(`${complex}`);
console.log(complex + '');
3. ToNumber 转换规则
Number(undefined);
Number(null);
Number(true);
Number(false);
Number('');
Number(' ');
Number('123');
Number('0x1A');
Number('0o17');
Number('0b1010');
Number('123abc');
Number('Infinity');
Number([]);
Number([1]);
Number([1,2]);
Number({});
Number(Symbol());
Number(42n);
4. ToString 转换规则
String(undefined);
String(null);
String(true);
String(false);
String(0);
String(-0);
String(NaN);
String(Infinity);
String(123);
String(Symbol('x'));
String(42n);
String([]);
String([1,2,3]);
String({});
String(new Date());
5. ToBoolean 与 Falsy 值
Boolean(false);
Boolean(0);
Boolean(-0);
Boolean(0n);
Boolean('');
Boolean(null);
Boolean(undefined);
Boolean(NaN);
Boolean([]);
Boolean({});
Boolean('0');
Boolean('false');
Boolean(new Boolean(false));
Boolean(Infinity);
Boolean(-Infinity);
6. == 的完整比较算法
ECMAScript §7.2.14 定义了 Abstract Equality Comparison:
[] == ![];
null == undefined;
null == 0;
NaN == NaN;
'' == 0;
'0' == false;
' \t\n' == 0;
[1] == 1;
['0'] == false;
完整算法流程
function abstractEquals(x, y) {
if (typeof x === typeof y) return x === y;
if (x == null && y == null) return true;
if (typeof x === 'number' && typeof y === 'string') return x === Number(y);
if (typeof x === 'string' && typeof y === 'number') return Number(x) === y;
if (typeof x === 'boolean') return abstractEquals(Number(x), y);
if (typeof y === 'boolean') return abstractEquals(x, Number(y));
if (typeof x === 'object' && (typeof y === 'number' || typeof y === 'string')) {
return abstractEquals(ToPrimitive(x), y);
}
return false;
}
7. 运算符中的隐式转换
1 + '2';
'3' + 4;
true + true;
[] + [];
[] + {};
{} + [];
'6' - 1;
'3' * '4';
'10' / '2';
'hello' - 1;
+'42';
+true;
+[];
+{};
比较运算符
'9' > '10';
9 > '10';
null > 0;
null >= 0;
null == 0;
const a = { valueOf() { return 1; } };
const b = { valueOf() { return 2; } };
console.log(a < b);
8. 实战案例
实战案例 1:安全的类型转换工具
const SafeConvert = {
toNumber(value, fallback = 0) {
if (value === null || value === undefined) return fallback;
const num = Number(value);
return Number.isNaN(num) ? fallback : num;
},
toString(value, fallback = '') {
if (value === null || value === undefined) return fallback;
if (typeof value === 'symbol') return value.description ?? fallback;
return String(value);
},
toBoolean(value) {
if (typeof value === 'string') {
return !['', '0', 'false', 'null', 'undefined'].includes(value.trim().toLowerCase());
}
return Boolean(value);
},
toInteger(value, fallback = 0) {
const num = this.toNumber(value, fallback);
return Math.trunc(num);
}
};
实战案例 2:JSON 解析中的类型安全
function parseApiResponse(raw) {
const data = JSON.parse(raw);
return {
id: +data.id || 0,
name: data.name != null ? String(data.name) : 'Unknown',
active: data.active === true || data.active === 'true' || data.active === 1,
tags: Array.isArray(data.tags) ? data.tags : [],
score: Number.isFinite(+data.score) ? +data.score : null,
};
}
实战案例 3:利用隐式转换的简洁写法
const tricks = {
intFromFloat: (n) => ~~n,
intFromFloat2: (n) => n | 0,
intFromFloat3: (n) => n >> 0,
toBool: (v) => !!v,
toNum: (s) => +s,
timestamp: () => +new Date(),
compact: (arr) => arr.filter(Boolean),
default: (a, b) => a || b,
nullish: (a, b) => a ?? b,
};
9. 深度追问
Q1:为什么 typeof null === 'object'?
这是 JavaScript 最早实现中的 bug。在第一版 JS 引擎中,值以 32 位存储,前 3 位作为类型标记:000 表示 object。而 null 被表示为全零(NULL 指针),所以类型标记匹配了 object。修复这个 bug 会破坏太多现有代码,因此永远保留。
Q2:Object.is vs === 的区别?
Object.is(NaN, NaN);
Object.is(+0, -0);
Object.is 实现了 SameValue 算法(§7.2.11),而 === 使用 Strict Equality Comparison(§7.2.16)。
Q3:document.all 为什么是 falsy 的?
document.all 是唯一的 "falsy object"。typeof document.all === 'undefined'。这是 HTML 规范为了向后兼容 IE 特性而设定的特殊行为(§7.1.2 注释),不遵循正常的 ToBoolean 规则。
10. 总结表格
| 值 | ToNumber | ToString | ToBoolean |
|---|
undefined | NaN | 'undefined' | false |
null | 0 | 'null' | false |
true | 1 | 'true' | true |
false | 0 | 'false' | false |
'' | 0 | '' | false |
'0' | 0 | '0' | true |
'1' | 1 | '1' | true |
[] | 0 | '' | true |
[0] | 0 | '0' | true |
{} | NaN | '[object Object]' | true |
NaN | NaN | 'NaN' | false |
| 最佳实践 | 说明 |
|---|
始终使用 === | 避免隐式转换的歧义 |
| 显式转换 | Number(x), String(x), Boolean(x) |
使用 ?? 替代 ` | |
Number.isNaN() | 而非全局 isNaN()(后者会先转 Number) |