JS数据类型转换=>分类来看更好些

242 阅读10分钟

众所周知,JavaScript 中有 7 种内置数据类型,分别是 6 种基本类型nullundefinednumberbooleanstringsymbol 以及 1 种复杂类型object

类型转换通常是指将从一种类型转换为另一种类型,通常被称为显式强制类型转换,比如:

let a = 42;
let b = String(a); // 显式强制类型转换
b // "42"

还有一种隐式强制类型转换,往往是由于一些操作而对某个产生的副作用导致的结果,比如:

let a = 42;
let b = a + ""; // 隐式强制类型转换
b // "42"

显式强制类型转换

从数据类型上来说,我们可以把值从基本类型转换为复杂类型(基本 => 复杂),可以使用原生函数(请注意 nullundefined 没有原生函数),比如:

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 => 0
  • undefined => 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 中的布尔值的假值有以下几个:

  • undefined
  • null
  • 布尔值 false
  • 数值 +0-0NaN
  • 字符串""

除此以外的值都会被转换为 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 - 0a * 1a / 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, 返回 aa && 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"; // falsetrue 转换为数值是 1,再进行数字和字符串的比较,"42" 转换为数值是 42)
      "42" == false; // falsefalse 转换为数值是 0,再进行数字和字符串的比较,"42" 转换为数值是 42)
      false == ""; // truefalse 转换为数值是 0,再进行数字和字符串的比较,"" 转换为数值是 0)
      false == 0; // truefalse 转换为数值是 0)
    
  • 对象和非对象进行比较,会把对象进行 ToPrimitive 操作(检查对象内部是否有 valueOf() 方法,如果有并且返回基本类型值则调用 valueOf() 并且将其返回值转换为数值作为结果,如果没有则调用 toString() 并且使用其返回的基本类型值转换为数值作为结果。)得到基本类型值再进行比较
      [] == ""; // true ([] 进行 ToPrimitive 操作得到 "")
      [] == "0"; // false ([] 进行 ToPrimitive 操作得到 "")
      [] == 0; // true ([] 进行 ToPrimitive 操作得到 "",再进行字符串和数值的比较,""转换为数值是 0)
      false == []; // truefalse 转换为数值是 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)
    
  • nullundefined 之间的相等比较:
    • nullundefined 之间可以相互进行隐式强制转换,因此这两个值相等
    • 其他非 nullundefinednullundefined 进行比较时,永远不相等
      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 操作,如果结果出现非字符串,就根据转换为数值的规则将双方强制转换为数字来进行比较

【图】不同的数据类型之间的比较

  • 红色:===

  • 橙色:==

  • 黄色:<= 和 >= 同时成立,== 不成立

  • 蓝色:只有 >=

  • 绿色:只有 <=