类型转换
让我们思想一个问题,类型转换是邪恶的吗?带着这个问题来看。
强制类型转换
某些情况下,我们希望将值显示的转换为我们期望的类型。
字符串的转换 ToString
原始值需要借助内建类型(原生类型), String() 来将一个非字符串类型强制转换为 string , String() 转换的过程是由语言规范ToString 抽象操作处理的。
参数类型 | 结果 |
---|---|
Undefined | “undefined” |
Null | “null” |
Boolean | 如果参数是 true,返回 “true”。参数为 false,返回 “false” |
Number | 又是比较复杂,可以看例子 |
String | 返回与之相等的值 |
布尔值的转换 ToBoolean
原始值需要借助内建类型(原生类型), Boolean() 来将一个非字符串类型强制转换为 boolean , Boolean() 转换的过程是由语言规范 ToBoolean 抽象操作处理的。
参数类型 | 结果 |
---|---|
false | false |
undefined | false |
null | false |
+0 | false |
-0 | false |
NaN | false |
‘’ | false |
除了这六种转为 false, 其余的都是为 true。
数值的转换 ToNumber
原始值需要借助内建类型(原生类型), Number() 来将一个非字符串类型强制转换为 number , Number() 转换的过程是由语言规范 ToNumber 抽象操作处理的。
参数类型 | 结果 |
---|---|
Undefined NaN | |
Null | +0 |
Boolean | 如果参数是 true,返回 1。参数为 false,返回 +0 |
Number | 返回与之相等的值 |
String | 这段比较复杂,看例子 |
Number 来转换一个字符串,会将其转为一个整数或浮点数,会忽略所有前导的 0, 如果有一个字符不是数字,结果都会返回 NaN。
原始值转对象 (看包装对象章节).
对象转原始值
对象不同, 一个对象要被强制转换为原始值 ,需要先通过 ToPrimitive 抽象操作, ToPrimitive 抽象操作去查询内部使用的 DefaultValue 操作,来查看是否有 valueOf 和 toString 方法。
当对象被用在需要转为原始值的上下文中,有三种变体: 被称为 “hint”.
- 当我们期望使用到字符串上下文的时候,对象会被转为字符串格式。 string hint
强制转换
String(obj);
隐式转换
alert(obj); // 字符串上下文
- 当我们做数学运算的时候,期待是一个数字上下文。 number hint
强制转换
Number(obj)
隐式转换(下面会讲到)
+obj;
obj1 - obj2;
obj1 > obj2;
- 当我们使用 二元加法 或者 == 时候,当运算符不确定期望值的时候。 将会依据 default hint 来转换。
obj1 + obj2;
obj1 == 1;
注意: > < 这样大小比较运算符,虽然可以使用字符串和数字。但是它们使用 number hint.
- 上面情况那么多,是不是记得很混乱
如果我们不是明确的强制转换,也就是我们明确的指出 hint, 如果是转换的日期类型,相当于传入 String ,否则,都相当于传入 Number。然后调用对应的 ToString 和 ToNumber.是不是就简单多了。
JavaScript 尝试查找并调用三个对象方法:
1. obj[Symbol.toPrimitive](hint) 带有 symbol 键 Symbol.toPrimitive, 如果这个方法存在.
2. 如果hint是一个字符串, 尝试 obj.toString() 和 obj.valueOf(), 只要有一个存在即可。
3. 如果hint是一个数字, 尝试 obj.valueOf() 和 obj.toString(), 只要有一个存在即可。
4. 转字符串调用的是 ToString,转数字调用 ToNumber。
5. 如果得不到原始值, JavaScript 抛出一个类型错误异常。
所以我们可以改变内建的 hint 行为
obj[Symbol.toPrimitive] = function (hint) {
// 可以在这里将对象转换为原始值
hint = 'string' || hint = 'number'
}
但是 Symbol.toPrimitive 是 ES6 出来的, 那么没有 Symbol.toPrimitive 的话,就是直接 2, 3, 4, 5 步骤。
隐式类类型转换
对于你来说,只要不是明确的类型转换都可以叫做它是隐式转换。
隐含的: String <-> Number
字符串和数字的转换
var a = '43';
var b = '0';
var c = 43;
var d = 0;
a + b; // '430'
c + d; // 43
二元加法运算符, 当其中一个已经是字符串了,就会进行加法拼接。
var a = [1, 2];
var b = [3, 4];
a + b; // '1,23,4'
但如果其中一个是对象,就会调用 ToPrimitive 抽象操作。带着 number 上下文来调用算法。valueOf() 返回对象本身,继续调用 toString() 返回一个逗号隔开的字符串。
我们可以利用只要有一方是字符串,就会进行字符串拼接,来进行数字转字符串
var a = 42;
var b = a + '';
b; // '42'; // 这种隐式转换还是很方便的。
但是注意:下面的两种转换结果是不一样的,因为它们带有的上下文是不一样的
除非你自定义了对象的操作,不然下面情况是很少能够干扰到你的。
更多常见的是 通过隐式这种来转换的。
var obj = {
valueOf() {
return 1;
},
toString() {
return 2;
}
}
obj + ''; // 隐式 1
String(obj); // 明确 2
隐含的: Boolean <-> Number
boolean 值到 number 会被转为 0或1. 这种转换可以让我们简单的做一些操作。
sum += argument[i] // 会转为数字
隐含的: * -> Boolean
- if () 语句中的测试表达式
- for () 的第二个字句
- while () 的循环测试表达式。
- ?: 三元表达式中的第一个子句。
- || && 操作符的左手边的操作数
任何不适 boolean 的值,都会调用 ToBoolean。
注意:
||、&& 会将所有布尔值的都为 false。
function foo(a) {
a = a || 'hello'; // 不好使,我传的是 空字符串
}
foo('');
这时候,我们可以使用 ?? ,只要不是 undefined 和 null 才会通过检测。
Symbol 明确转换
从一个symbol到一个string的 明确 强制转换是允许的.
但是相同的 隐含 强制转换是不被允许的,而且会抛出一个错误。
var s1 = Symbol('cool');
String(s1); // 'Symbol(cool)'
var s2 = Symbol('not');
s2 + ''; // TypeError
Symbol 可以明确或者隐含的转为 Boolean, 总是 true;
双等于
var a = 42;
var b = '42';
a == b; // true
如果Type(x)是Number而Type(y)是String, 返回比较x == ToNumber(y)的结果。
如果Type(x)是String而Type(y)是Number, 返回比较ToNumber(x) == y的结果。
var a = 1;
var b = true;
a == b; // true
如果Type(x)是Boolean, 返回比较 ToNumber(x) == y 的结果。
如果Type(y)是Boolean, 返回比较 x == ToNumber(y) 的结果。
所以我们在用 true/false 的时候,要使用严格等于。
var a = '42';
if(a == true) {} // 不好,会失败的
if (a === true) {} // 不好, 会失败
if (a) {} // 能够隐含的转换
if (!!a) {} //明确的工作
if (Boolean(a)) {} // 明确的工作
null == undefined;
如果x是null而y是undefined,返回true。
如果x是undefined而y是null,返回true。
比较:object与非object
如果Type(x)是一个String或者Number而Type(y)是一个Object, 返回比较 x == ToPrimitive(y) 的结果。
如果Type(x)是一个Object而Type(y)是String或者Number, 返回比较 ToPrimitive(x) == y 的结果。
疯狂的情况
[] == ![]; // true -> [] == false 0 = 0
2 == [2] // true
'' == [null] ; // true '' == ''
0 == '\n'; // true
我们应该注意的(常会踩坑的)
'' == 0; // 0 == 0;
'' == []; // '' == 0 0 == 0
0 == []; // 0 == 0
下面的这种,我们应该避免使用,以防止我们踩坑。
if (a == '') {}
if (a == b) {}
安全使用建议
- 如果比较的任意一边可能出现true或者false值,那么就永远,永远不要使用==。
- 如果比较的任意一边可能出现[],"",或0这些值,那么认真地考虑不使用==