JavaScript 的显式转换和隐式转换(超详)

1、js数据类型

最新的ECMAScript 标准定义了8 种数据类型。

7种基本类型(也就是原始值):

Undefined、Null、Boolean、Number、String、Symbol(es6)、BigInt(es10)(这里不讨论 Symbol、BigInt 两种类型。)

1种对象类型:

Object

2、类型转换

数据类型间的转换可分为:

  • 原始值间的转换:转换为Boolean、Number、String
  • 原始值转换为对象:转换为Object
  • 对象转换为原始值:有两种转换方式

这里着重解释下对象转换为原始值的转换规则。

2.1 对象转换为原始值

Js引擎内部的抽象操作ToPrimitive(转换为原始值)的方法大体如下:

/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)

type 表示期望对象转换为的原始数据类型,分为 Number 和 String。

若type被标记为String(对象到字符串的转换):

  1. 若对象有 toString() ,则调用,若返回结果为原始值,则进一步转换为字符串(若本身不是字符串)并返回;
  2. 若对象没有 toString() 或返回的不是一个原始值,那么调用 valueOf() ,若结果为原始值,后续同上;
  3. 都无法获得原始值,那么抛出TypeError 异常;

若type被标记为Number(对象到数字的转换):

  1. 若对象有 valueOf() ,则调用,若返回结果为原始值,则进一步转换为数字(看情况转换,非必须)并返回;
    [123] == "123" // true
    [123] == 123   //true
    
  2. 否则,若对象具有 toString() ,则调用,若返回结果为原始值,后续同上;
  3. 都无法获得原始值,那么抛出TypeError 异常;

type 是个可选参数,不传入时,默认按照下面规则自动设置:

  1. 若对象为 Date 类型,则 type为 String;
  2. 否则 type 为 Number;

这里,Date 类型的转换和上文讲述的转换不完全一致。它调用 valueOf() 或 toString() 返回的原始值将被直接使用,而不会被强制转换为数字或字符串。

2.1.1 valueOf() 和 toString() 的返回值解析

控制台输出 Object.prototype,就可以看到 valueOf() 和 toString() 。而所有对象继承自Object,因此所有对象都继承了这两个方法。

对 Js 常见的内置对象:基本包装类型(Number、String、Boolean)、单体内置类型(global、Math)、Date、Array、Function、RegExp,分别使用 toString() 和 valueOf() 得到的返回结果,做进一步分析。

valueOf():

  • 基本包装类型直接返回原始值
  • Date 类型返回毫秒数
  • 其他都返回对象本身:由于大多对象是复合值,无法真正表示为一个原始值,因此返回对象本身。
// 1. 基本包装类型直接返回原始值

var num = new Number('123');
num.valueOf(); // 123

var str = new String('123abc');
str.valueOf(); // '123abc'

var bool = new Boolean('abc');
bool.valueOf(); // true

// 2. Date 类型返回一个内部表示:1970年1月1日以来的毫秒数

var date = new Date();
date.valueOf(); // 1608784980823

// 3. 返回对象本身

var obj = new Object({});
obj.valueOf() === obj; // true

var arr = new Array();
arr.valueOf() === arr; // true

var reg = new RegExp(/a/);
reg.valueOf() == reg; // true

var func = function() {};
func.valueOf() == func; // true

// 单体内置类型
global.valueOf() == global; // true
Math.valueOf() == Math; // true

toString()

作用是返回一个反应该对象的字符串。

  • 基本包装类型直接返回原始值
  • 默认的 toString() 并不会返回看起来有直观意义的值,例如 [object Object]
  • 但很多类都有实现各自版本的 toString(),例如日期、数组、正则表达式、函数。
Number.prototype.hasOwnProperty('toString');   // true
String.prototype.hasOwnProperty('toString');   // true 
Boolean.prototype.hasOwnProperty('toString');  // true
Date.prototype.hasOwnProperty('toString');     // true 返回可读的日期和时间字符串
Array.prototype.hasOwnProperty('toString');    // true 将每个元素转换为字符串
RegExp.prototype.hasOwnProperty('toString');   // true 返回表示正则表达式的字符串
Function.prototype.hasOwnProperty('toString'); // true 返回这个函数定义的 Javascript 源代码字符串

// 1. 基本包装类型返回原始值

var num = new Number('123abc');
num.toString(); // 'NaN'

var str = new String('123abc');
str.toString(); // '123abc'

var bool = new Boolean('abc');
bool.toString(); // 'true'

// 2. 默认的 toString()

var obj = new Object({});
obj.toString();   // "[object Object]"
global.toString() // "[object Window]"
Math.toString();  // "[object Math]"

// 3. 类自己定义的 toString()

// Date类型转换为可读的日期和时间字符串
var date = new Date();
date.toString(); // "Wed Oct 11 2017 08:00:00 GMT+0800 (中国标准时间)"

// 将每个元素转换为字符串
var arr = new Array(1,2);
arr.toString(); // '1,2'

// 返回表示正则表达式的字符串
var reg = new RegExp(/a/);
reg.toString(); // "/a/"

// 返回这个函数定义的 Javascript 源代码字符串
var func1 = function () {}
func1.toString(); // "function () {}"

function func2() {}
func2.toString(); // "function func2() {}"

对于日期类型,转换为日期的字符串形式比毫秒数来得有意义,因此 type 默认为 String。

2.2 类型转换表分析和归纳

JavaScript类型转换表,参自《JavaScript权威指南》
在这里插入图片描述
这里的对象类型(Object)转换为原始值的结果,按默认方式显示(即 Date 标记为 String,其他对象类型标记为 Number)

  • 转换为字符串:
    1. Undefined、Null 没有 toString() 会报错,可用 String() 打印;

    2. Boolean、Number 直接打印 toString() 后的结果;
      a. Number:不过那些极小和极大的数字会使用指数形式

    3. Symbol 类型的值直接转换,但是只允许显式强制类型转换,使用隐式强制类型转换会产生错误

    4. 对象类型:除非自行定义 toString() 方法,否则会调用默认的 toString()
      a. 数组:[ ] => “”,[9] => “9”,[“a”, “b”, “c”] => “a,b,c”;

      补充: [undefined] == '' // true
      自红宝书:如果数组中的某一项的值是 null 或者 undefined,那么该值在 join()、toLocaleString()、toString()和 valueOf()方法返回的结果中以空字符串表示。
      

      b. 函数:返回这个函数定义的 Javascript 源代码字符串
      c. 正则表达式:返回正则表达式的字符串
      d. 日期:返回可读的日期和时间字符串
      e. 基本包装类型:同其原始值返回的结果
      f. 单体内置类型:global => “[object Window]”,Math => “[object Math]”

  • 转换为数值:
    1. Undefined => NaN,null => 0;
    2. Boolean:true => 1,false => 0;
    3. String:(同调用Number())"" => 0,‘1.2’ => 1.2,‘one’ => NaN;
    4. Symbol 类型的值不能转换为数字,会报错。
    5. 对象类型:
      a. 数组:[ ] => “” => 0,[9] => “9” => 9,[“a”, “b”, “c”] => “a,b,c” => NaN;
      b. 除了数组的前两种情况,其他都为NaN
  • 转换为布尔值:
    1. Undefined、Null、""、正负0、NaN,这五种转换为布尔值时,都会变成 false;
    2. 其他,包括对象类型都为 true;
  • 原始值转换为对象:
    1. Undefined、Null 转换为对象时报错,而使用 Object() 在这种情况下,不会报错,只是返回一个空对象;
    2. 其他的原始值通过调用String()、Number() 或 Boolean() 构造函数,转换为基本包装类型;

3、显式转换

人为的转换,使得代码看起来更清晰易读。

显式转换,最简单的方式是使用函数:

  • 转换为字符串:toString() 或 String()
  • 转换为数值:Number()、parseInt()、parseFloat()
  • 转换为布尔值:Boolean()
  • 转换为对象:Object()

来测试一下吧

String(false) // "false" 布尔值转换为字符串
Number("123") // 123 字符串转换为数值 
Boolean([])   // true 对象类型转换为布尔值
Object(123)   // new Number(123) 原始值通过Number()构造函数转换为基本包装类型

4、隐式转换

当运算符在运算时,两边数据不统一,编译器会自动将两边数据进行数据类型转换成统一的再计算。

常见的隐式转换:

  • 逻辑语句的类型转换:当使用if、while、for 时,隐式转换为布尔值;
  • 逻辑表达式:
    • ! 逻辑非,隐式转换为布尔值,并取反,!!两次逻辑非,隐式转换为布尔值;
    • || 和 && 会将非布尔值操作数,隐式转换为布尔值,再判断;
  • 算术表达式
    • 递增递减++ --,一元加减 +a、- a(结果的符号取反),二元 -,乘法,除法,求模,隐式转换为 Number;
    • +运算符:
      • 不同类型间的转换规则
        1. 如果其中一个操作符是对象,则进行对象到原始值的转换(这里是通过 toString 方法获取);
        2. 进行了对象到原始值的转换后,如果其中一个操作数是字符串,则另一个操作数也会转换成字符串,再进行连接;
        3. 否则两个操作数都转换为数字(或者NaN),然后进行加法操作。
      • 具体使用时,需要考虑加法的结合性对运算顺序的影响
       1 + 2 + "hh"   // "3hh"
       1 + (2 + "hh") // "12hh"
      
  • == 运算符:
    • 注意:尽管 if 语句会隐式转换为布尔值,但 == 运算符并不会自动得将操作数转换为布尔值。
    • 不同类型间的转换规则
    1. null == undefined 为true,和其他的比较都为false;

    2. 如果一个值是数字,另一个是字符串,先将字符串转换为数字,再进行比较;
      a. NaN 与所有值都不相等,包括它自己,可使用 isNaN() 来判断一个值是否是数字;

      console.log(NaN=="dsdd");  // false 字符串"dsdd"转为数字为NaN,但是NaN != NaN
      

      b. 数字间比较,以0开头是8进制;

      console.log(012==10);  // true
      console.log(099==99);  // true 这种情况是因为八进制中不可能出现9,所以看成一个十进制
      
    3. 如果一个值是布尔值,转换为数字再比较;

      true == '2'  // false, 先把 true 变成 1, '2' 变成 2
      
    4. 如果一个值是对象,另一个是数字或字符串或 symbol,将对象转换为原始值再比较(调用 valueOf 方法);
      a. 注意,同类型比较时,对象间除非指针一样,否则它就是两个不同的对象;

    5. 其他不同类型比较均不相等;

来三个例子分析下:

// + 运算符
1 + {} =// "1[object Object]" 
对象(到原始值的转换)转换为字符串后进行连接
分析:
1. {} => "[object Object]" 规则1:如果其中一个操作符是对象,转换为原始值,此时为 1 + "[object Object]"
2. 1 => "1" 规则2:转换后,若其中一个操作数是字符串,另外一个也转换为字符串,再进行连接,此时为 "1[object Object]"

// == 运算符
"1" == true // true
分析:
1. true => 1 规则3:如果一个值是布尔值,转换为数字再比较。 此时转换为 "1" == 1
2. "1" => 1 规则2:如果一个值是数字,另一个值是字符串,则字符串转换数字再比较,此时 1 == 1

// * 运算符
["123"] * {} // NaN
分析:
1.*:两边的操作符会隐式转换为 Number
2. ["123"] 执行对象到数字的转换,type 为 Number
   2.1 ["123"] 先执行 valueOf() 返回对象本身
   2.2 再通过 toString() 得到原始值(字符串) "123",进一步转换为数字 123
3. {} 执行对象到数字的转换,type 为 Number
   3.1 同理得到原始值(字符串) "[object Object]",进一步转换为数字 NaN
4. 结果 123 * NaN = NaN

在代码中,常用到的隐式转换:

x + ""  // 等价于 String(x)
+x      // 等价于 Number(x),也可以写成 x - 0
!!x     // 等价于Boolean(x)
!x      // 转换为布尔值,并取反

你可能感兴趣的:(前端,隐式转换,javascript,显式转换)