众所周知,JavaScript 中有 7 种内置数据类型,分别是 6 种基本类型: null 、 undefined 、 number 、 boolean、 string 、 symbol 以及 1 种复杂类型: object。
类型转换通常是指将值从一种类型转换为另一种类型,通常被称为显式强制类型转换,比如:
let a = 42;
let b = String(a); // 显式强制类型转换
b // "42"
还有一种隐式强制类型转换,往往是由于一些操作而对某个值产生的副作用导致的结果,比如:
let a = 42;
let b = a + ""; // 隐式强制类型转换
b // "42"
显式强制类型转换
从数据类型上来说,我们可以把值从基本类型转换为复杂类型(基本 => 复杂),可以使用原生函数(请注意 null 和 undefined 没有原生函数),比如:
let a = "hello";
<!-- 方式1 -->
let b = new String(a); // 使用 String 构造函数
<!-- 方式2 -->
let c = Object(a); // 可以不带 new 关键字
b // String {"hello"}
c // String {"hello"}
typeof b // "object"
typeof c // "object"
通常并不会去写这样的代码,但是如果提到封箱和拆箱或许你就会感到有点儿熟悉,简单来讲,当我们对基本类型值使用一些属性或方法时,JavaScript 引擎会自动地为基本类型值封箱(包装成一个封装对象),以访问这些属性或方法。比如:
let a = "hello";
a.length; // 5
a.toUpperCase(); // "HELLO"
而当需要用到对象的基本类型值时,会为对象进行拆箱,比如:
let a = new String( "hello" );
var b = a + "";
b; // "hello"
typeof a; // "object"
typeof b; // "string"
或者把值从复杂类型转换为基本类型(复杂 => 基本),我们需要着重了解三种转换:
- 转换为字符串
- 转换为数值
- 转换为布尔值
转换为字符串
可以直接调用原生的 String() 方法(注意没有 new 关键字)
String(626); // "626"
转换为字符串的规则为:
null=>"null"undefined=>"undefined"- 字符串 => 返回本身
- 布尔值
true=>"true"/false=>"false" - 数值
12=>"12"(通常采用通用规则),极大值或极小值会使用指数形式String(12); // "12" String(1000000000000000000000); // "1e+21" - 引用类型(对象) => 会调用对象的
toString()方法(如果对象自定义了toString()则会调用自定义的toString()并且把它的返回值作为结果,比如数组重写toString()为把所有元素转为字符串再用 “,” 拼接作为返回值[1,2,{b:333}]=>"1,2,[object Object]")String({}); // "[object Object]" String([]); // ""
还可以调用 toString() 把值转换为字符串(要注意如果对基本类型值使用时,因为会创建封装对象,所以涉及了隐式转换)
let d = [1, 2, {c:"hello"}];
d.toString(); // "1,2,[object Object]"
转换为数值
可以直接调用原生的 Number() (注意没有 new 关键字)把值转换为数值
Number("626"); // 626
转换为字符串的规则为:
null=>0undefined=>NaN- 数值 => 十进制返回本身,其他返回同等大小的十进制
Number("12"); // 12 Number("0626"); // 406 Number("0xf"); // 15 - 布尔值
true=>1/false=>0 - 字符串
"12"=>12(被操作数可以解析为数值),"12a"=>NaN(被操作数无法解析为数值)- 如果字符串中只包含数字,转换为十进制数值;如果是有效的十六进制格式,例如,则将其他转换为相同大小的十进制整数值;(前导 0 都会被忽略)
- 如果字符串是空的(不包含任何字符),则将其转换为
0; - 如果字符串中包含除上述格式之外的字符,则将其他转换成
NaN.Number("-0626"); // -626 Number("0xf"); // 15 Number(""); // 0 Number("626hello"); // NaN
- 引用类型(对象) => 进行
ToPrimitive抽象操作,检查对象内部是否有valueOf()方法,如果有并且返回基本类型值则调用valueOf()并且将其返回值转换为数值作为结果,如果没有则调用toString()并且使用其返回的基本类型值转换为数值作为结果。但是如果valueOf()和toString()均不返回基本类型值,会产生TypeError错误。
Number( [] ); // 0 (toString() 返回的是[],所以继续调用 toString()得到了"",再强制转换为数值)
let b = {
toString: function(){
return {};
}
};
Number(b); // Uncaught TypeError: Cannot convert object to primitive value
还可以调用 parseInt() 、 parseFloat() 可以把字符串解析为数值,与转换的区别是解析允许字符串含有非数字字符,从左到右依次识别,如果遇到非数字字符就停止。而转换只要字符串包含非数字字符,就会返回 NaN。如果参数不是字符串,则会先将参数强制转换为字符串再进行解析。
parseInt("123hello"); // 123
parseInt({}); // NaN
parseFloat("123.4hello"); // 123.4
parseInt( false, 16 ); // 250 ("fa" 来自于 "false")
一元 + 运算符也会显式地将值转换为数字(考虑代码可读性,本人建议用其他的方式来转换为数值)
let a = "123"
+a // 123
let b = []
+b // 0
+({}); // NaN
同样的 - 运算符也可以将值转换为数字,但是会反转符号位,因此需要使用 - - (注意不是 -- ,这会被当作递减运算符)(考虑代码可读性,本人建议用其他的方式来转换为数值)
let a = "123"
- -a // 123
let b = []
- -b // 0
- -({}); // NaN
转换为布尔值
可以直接调用原生的 Boolean() (注意没有 new 关键字)方法把值转换为布尔值
Boolean(1); // true
JavaScript 中的布尔值的假值有以下几个:
undefinednull- 布尔值
false - 数值
+0、-0和NaN - 字符串
""
除此以外的值都会被转换为 true,(所以即使是封装了假值的对象(本质上还是对象)再转换为布尔值得到的自然也是 true),
Boolean(" "); // true
Boolean({}); // true
Boolean([]); // true
let a = new Boolean( false );
Boolean(a); // true
还可以用 ! 显式转换为布尔值,但是 ! 得到的是一个被反转的结果,因此可以使用 !! 显式转换为布尔值。
!![]; // true
!!0; // false
!!null; // false
隐式强制类型转换
数学运算 + 、-、 *、 /
var a = 42;
var b = "0";
var c = [];
var d = {};
var e = false;
a + a; // 84
a + c; // "42"(空数组被转换为"")
a + b + c + d; // "420[object Object]false"(空数组被转换为"")
对于 + 运算符,包含一个或多个字符串或者能够转换为字符串的话(采用转换为字符串的规则),则逻辑为字符串拼接,即会隐式的把不是字符串的值转换为字符串,否则采用加法运算。
或许你会想到用 a + "" 把值转换为字符串,这样是可以的,但是与直接调用 String(a) 是不一样的,前者先调用 valueOf(),后者直接调用 ToString()
let a = {
valueOf: function() { return 42; },
toString: function() { return 4; }
};
a + ""; // "42"
String( a ); // "4"
再看看隐式转换为数值的情况,- 是数字减法运算符,因此 a - 0、a * 1 、a / 1 可以用于将值隐式转换为数值。(采用转换为数字的规则)
let a = "4";
let b = [3]; // 转换为 3
let c = {}; // 转换为 NaN
a - 0; // 4
a * 1; // 4
a / 1; // 4
a - b // 1 (精度问题)
b - 0; // 3
b * 1; // 3
c - 0; // NaN
b / 1; // NaN
条件判断
以下场景都可能涉及到隐式强制转换,值会被隐式强制类型转换为布尔值(采用转换为布尔值的规则):
if (..)语句中的条件判断表达式。for ( .. ; .. ; .. )语句中的条件判断表达式(第二个)。while (..)和do..while(..)循环中的条件判断表达式。? :三元运算符的条件判断表达式。- 作为条件判断表达式时的逻辑运算符
||(逻辑或)和&&(逻辑与)左边的操作数。(会对左边操作数执行条件判断,如果其不是布尔值就先进行强制转换为布尔值,然后再执行条件判断- 注意:
||和&&本身返回的是某个操作数的值,而不是条件判断的结果值。 - 对于
||来说,如果条件判断结果为true就返回第一个操作数的值,如果为false就返回第二个操作数的值。 &&则相反,如果条件判断结果为true就返回第二个操作数的值,如果为false就返回第一个操作数的值。var a = 42; var b; var c = null; if ((a && c) || b) { console.log( "yep" ); // yep (要注意逻辑运算后 if 还会执行一次隐式转换) } a || b; // 42 (条件判断 a 为 true, 返回 a) a && c; // null (条件判断 a 为 true, 返回 c)
- 注意:
宽松相等 == 和宽松不相等 !=
在 ES5 规范中可以找到抽象相等比较算法。(宽松相等 == 和严格相等 === 的区别是宽松相等可能会包含隐式强制类型转换,而严格相等不会。)
- 如果两个值类型相同,就直接进行比较。
- 特殊情况
NaN不等于NaN - 特殊情况
+0等于-0 - 如果两个值都是对象则比较它们指向的地址。如果指向同一个地址则视为相等,返回
true, 否则返回false
NaN == NaN; // false +0 == -0; // true [] == []; // false - 特殊情况
- 字符串和数值进行比较时,会把字符串转换成数值再进行比较
42 =="42"; // true ( "42" 转换为数值是 42) "" == 0; // true ( "" 转换为数值是 0) - 布尔值和其他类型值进行比较时,会把布尔值转换成数值再进行比较
true == "42"; // false (true 转换为数值是 1,再进行数字和字符串的比较,"42" 转换为数值是 42) "42" == false; // false (false 转换为数值是 0,再进行数字和字符串的比较,"42" 转换为数值是 42) false == ""; // true (false 转换为数值是 0,再进行数字和字符串的比较,"" 转换为数值是 0) false == 0; // true (false 转换为数值是 0) - 对象和非对象进行比较,会把对象进行
ToPrimitive操作(检查对象内部是否有valueOf()方法,如果有并且返回基本类型值则调用valueOf()并且将其返回值转换为数值作为结果,如果没有则调用toString()并且使用其返回的基本类型值转换为数值作为结果。)得到基本类型值再进行比较[] == ""; // true ([] 进行 ToPrimitive 操作得到 "") [] == "0"; // false ([] 进行 ToPrimitive 操作得到 "") [] == 0; // true ([] 进行 ToPrimitive 操作得到 "",再进行字符串和数值的比较,""转换为数值是 0) false == []; // true (false 转换为数值是 0,再进行数字和[]的比较,[] 进行 ToPrimitive 操作得到 "",再进行字符串与数字的比较,""转换为数值是 0) [] == ![] // true (注意右值被显式地转换为了布尔值 false,转换为数值是0,[] 进行 ToPrimitive 操作得到 "",再进行字符串与数字的比较,""转换为数值是 0) var a = { valueOf: function(){ return 222 }, toString: function(){ return 444 } }; a == "222"; // true (a 进行 ToPrimitive 操作得到 222,再进行字符串和数值的比较,"222"转换为数值是 222) a == 222; // true (a 进行 ToPrimitive 操作得到 222) a == "444"; // false (a 进行 ToPrimitive 操作得到 222,再进行字符串和数值的比较,"444"转换为数值是 444) null和undefined之间的相等比较:null和undefined之间可以相互进行隐式强制转换,因此这两个值相等- 其他非
null或undefined与null或undefined进行比较时,永远不相等null == undefined; // true undefined == undefined; // true null == null; // true null == {}; // false null == []; // false
抽象关系比较
主要分为以下两种
- 字符串之间的比较,则按字母顺序来进行比较
var a = [ "42" ]; (ToPrimitive 得到"42")
var b = [ "043" ]; (ToPrimitive 得到"043")
a < b; // false ("0" 在字母顺序上小于 "4")
var a = { b: 42 }; (ToPrimitive 得到" [object Object]")
var b = { b: 43 }; (ToPrimitive 得到" [object Object]")
a < b; // false (字母顺序上不成立)
a == b; // false (指向的地址不同)
a > b; // false (字母顺序上不成立)
a <= b; // true (“<=” 实际上是对 “>” 取反,得到true)
a >= b; // true (“>=” 实际上是对 “<” 取反,得到true)
- 其他情况,比较双方首先执行 ToPrimitive 操作,如果结果出现非字符串,就根据转换为数值的规则将双方强制转换为数字来进行比较
【图】不同的数据类型之间的比较
-
红色:===
-
橙色:==
-
黄色:<= 和 >= 同时成立,== 不成立
-
蓝色:只有 >=
-
绿色:只有 <=