本文深入解析 JavaScript 中的类型转换机制,涵盖基本类型与引用类型向布尔、字符串及数字的转换规则。重点剖析 == 运算符的隐式转换逻辑,包括 null、undefined、NaN 及对象类型的处理细节,帮助开发者理解常见面试题背后的原理,避免类型陷阱。
引言:类型转换与面试题
讨论类型转换的动机源自一道经典的面试题。
题目内容如下:
var bool = new Boolean(false);
if (bool) {
alert('true');
} else {
alert('false');
}
运行结果出乎意料,弹出的是 'true'。
其实类型转换、操作符优先级等基础概念,犀牛书均有详述,但我极少翻阅其前五章。
关于操作符优先级,许多资料建议无需死记硬背,遇到不确定的情况加上括号即可。日常编码中,这种做法确实能避免很多问题。
然而,面试场景往往要求开发者直接处理这类问题,其考察意义值得商榷。
暂且放下抱怨,本文旨在系统梳理类型转换规则,目标是将《JavaScript 权威指南》第 49 页的相关表格内容内化。
基本类型的假值与真值
首先明确哪些值在布尔上下文中被视为假值(falsy)。
共有 6 个假值:
0或+0、-0,NaN
""
false
undefined
null
上述列表按基本类型排列。
除此之外的所有值,无论形式如何,均为真值(truthy)。例如:
Infinity
'0'、'false'、" "(空格字符)
任何引用类型:[],{},function(){}
需要正确理解 `if (a && b)` 的逻辑:先对 `a && b` 表达式进行求值,再将求值结果转换为 Boolean 类型。
`&&` 是短路运算符,求值结果不一定是 Boolean 类型,更不是将两边先转为布尔值再运算。
例如 `2 && 3` 的结果是 `3`,而非 `true`。
因此,将 `if (a && b)` 理解为“如果 a 和 b 同时为真”是不准确的描述。
基本类型向字符串和数字的转换
其他基本类型转换为字符串时,结果通常符合预期:
console.log("" + null); // "null"
console.log("" + undefined); // "undefined"
console.log("" + false); // "false"
console.log("" + true); // "true"
console.log("" + 0); // "0"
console.log("" + NaN); // "NaN"
console.log("" + Infinity); // "Infinity"
基本类型转换为数字时,部分情况需要特殊记忆:
console.log(+null); // 0
console.log(+undefined); // NaN
console.log(+false); // 0
console.log(+true); // 1
console.log(+""); // 0
console.log(+'1'); // 1
console.log(+'1x'); // NaN
注意:`null` 转换为数字是 `0`,空字符串 `""` 也是 `0`,而 `undefined` 转换为数字是 `NaN`。
以上涵盖了基本类型的转换规则。
引用类型向基本类型的转换
接下来讨论引用类型(对象)转换为基本类型的规则。
引用类型转换为布尔值时,始终为 `true`。
引用类型转换为字符串的规则如下:
1. 优先调用 `toString` 方法(如果存在),检查返回值是否为原始类型。如果是,将其转换为字符串并返回。
2. 否则,调用 `valueOf` 方法(如果存在),检查返回值是否为原始类型。如果是,将其转换为字符串并返回。
3. 若上述步骤均失败,则抛出错误。
引用类型转换为数字的规则如下:
1. 优先调用 `valueOf` 方法(如果存在),检查返回值是否为原始类型。如果是,将其转换为数字并返回。
2. 否则,调用 `toString` 方法(如果存在),检查返回值是否为原始类型。如果是,将其转换为数字并返回。
3. 若上述步骤均失败,则抛出错误。
首先观察常见引用类型的 `toString` 和 `valueOf` 返回值:
var a = {};
console.dir(a.toString()); // "[object Object]"
console.dir(a.valueOf()); // 对象本身
var b = [1, 2, 3];
console.dir(b.toString()); // "1,2,3"
console.dir(b.valueOf()); // 对象本身
var c = [[1],[2]];
console.dir(c.toString()); // "1,2"
console.dir(c.valueOf()); // 对象本身
var d = function() {return 2};
console.dir(d.toString()); // "function() {return 2}"
console.dir(d.valueOf()); // 对象本身
基于上述方法,对应的转换结果如下:
var a = {};
console.dir(a + ""); // "[object Object]"
console.dir(+a); // NaN
var b = [1, 2, 3];
console.dir(b + ""); // "1,2,3"
console.dir(+b); // NaN
var c = [[1],[2]];
console.dir(c + ""); // "1,2"
console.dir(+c); // NaN
var d = function() {return 2};
console.dir(d + ""); // "function () {return 2}"
console.dir(+d); // NaN
如果自定义方法返回非原始类型,则会报错:
var a = {};
a.toString = function() {return {};}
console.log("" + a); // 报错
console.log(+a) // 报错
至此,类型转换的基本规律已阐述完毕。
== 运算符的隐式转换规则
最后讨论常被诟病的 `==` 运算符。
一道相关面试题如下:
var a = false;
var b = undefined;
if (a == b) {
alert('true');
} else {
alert('false');
}
直觉上可能认为会弹出 `true`,但实际结果是 `false`。
原因如下:
双等号在两侧类型不同时,会执行隐式转换。《JavaScript 权威指南》第 75 页总结如下:
1. `null` 和 `undefined` 相等。
2. 数字与字符串比较时,将字符串转换为数字后再比较。
3. 若涉及 `true` 或 `false`,将其转换为 `1` 或 `0` 后再比较。
4. 若涉及引用类型,优先调用 `valueOf` 方法。
5. 其他情况均不相等。
因此得出以下结论:
console.log(undefined == false); // false
console.log(null == false); // false
console.log(0 == false); // true
console.log(NaN == false); // false
console.log("" == false); // true
`0 == false` 为 `true`,依据第 3 条规则。
`"" == false` 为 `true`,依据第 3 条,`false` 转为 `0`,变为 `"" == 0`;再依据第 2 条,`""` 转为 `0`,最终 `0 == 0` 为 `true`。
第 4 条规则的一个示例:
console.log([[2]] == '2')
上述结果为 `true`,原因分析如下:
`[[2]]` 的 `valueOf` 返回对象本身,非原始类型。
尝试调用 `toString`,结果为 `'2'`。
因此比较变为 `'2'` 与数字 `2` 的比较。依据第 2 条,字符串 `'2'` 转换为数字 `2`,两者相等。
最后建议:使用 `===` 可以避免上述隐式转换带来的问题。
以上介绍了 JavaScript 类型转换的核心机制,希望对读者有所帮助。如有任何疑问,欢迎留言讨论,小编会及时回复。感谢支持!