类型转换

让我们思想一个问题,类型转换是邪恶的吗?带着这个问题来看。

强制类型转换

某些情况下,我们希望将值显示的转换为我们期望的类型。

字符串的转换 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”.

  1. 当我们期望使用到字符串上下文的时候,对象会被转为字符串格式。 string hint
强制转换
String(obj); 
隐式转换
alert(obj);  // 字符串上下文
  1. 当我们做数学运算的时候,期待是一个数字上下文。 number hint
强制转换
Number(obj)
隐式转换(下面会讲到)
+obj;
obj1 - obj2;
obj1 > obj2;
  1. 当我们使用 二元加法 或者 == 时候,当运算符不确定期望值的时候。 将会依据 default hint 来转换。
obj1 + obj2; 
obj1 == 1;

注意: > < 这样大小比较运算符,虽然可以使用字符串和数字。但是它们使用 number hint.

  1. 上面情况那么多,是不是记得很混乱
    如果我们不是明确的强制转换,也就是我们明确的指出 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 的话,就是直接 2345 步骤。

隐式类类型转换

对于你来说,只要不是明确的类型转换都可以叫做它是隐式转换。

隐含的: 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

  1. if () 语句中的测试表达式
  2. for () 的第二个字句
  3. while () 的循环测试表达式。
  4. ?: 三元表达式中的第一个子句。
  5. || && 操作符的左手边的操作数

任何不适 boolean 的值,都会调用 ToBoolean。

注意:

||&& 会将所有布尔值的都为 falsefunction 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) {} 

安全使用建议

  1. 如果比较的任意一边可能出现true或者false值,那么就永远,永远不要使用==。
  2. 如果比较的任意一边可能出现[],"",或0这些值,那么认真地考虑不使用==