一篇读懂JavaScript数据类型转换

174 阅读10分钟

引入

从C语言学到JavaScript,想必读者了解过不少语言,对于数据类型与数据类型转换也有所了解。与众多语言相似的是,JavaScript也存在数据类型转换的问题。JavaScript的数据类型转换的运用广泛、实现并不困难,但想要完全了解实现原理还是有些头疼的。相信读过这篇文章后,这一问题不再能难倒你。

本篇谈论的数据类型

在针对数据类型转换进行介绍之前,先明确讨论涉及的数据类型:ES6之前的数字(number)、字符串(string)、布尔(boolean)、undefined、null五种原始类型以及引用类型。本篇文章所讨论的数据类型转换发生于这六种数据类型之间,按转换方式分为显示转换和隐式转换。与此同时,本篇文章不讨论向其他类型向undefined、null的转换。这两种数据类型无法构造其他值,没有转换意义,不作讨论。

显示转换

显示转换可按转换前的数据类型分为原始值的转换和对象的转换,分别将原始值和对象转为除本身类型以外的数据类型。

原始值的转换

在JavaScript中,万物皆对象。追其根本,每一个原始值都由其对应原始数据类型的构造函数构造而来。因此原始值的转换就是将原始值作为参数,调用目的类型的构造函数进行构造,得到一个新的目的类型变量,数据类型转换就完成了。接下来以各种数据类型为例向布尔、数字、字符串、对象进行数据类型转换,并对相应转换作出解释、标注重点。

原始值创建

let str = 'hello'
let num = 123
let bool = false
let un = undefined
let nu = null

原始值转布尔类型

原始值显示转布尔类型通过调用Boolean()函数实现,转换规则如下:

◎字符串转布尔:空字符串转为false,其余为ture

◎数字转布尔:找得到值的范围内,只有0转为false,其余都是true

◎undefined 和 null 转布尔:都转为false

let str1 = ''
let num1 = 0
let num2 = NaN

console.log(Boolean(str));//true
console.log(Boolean(str1));//空字符转为false

console.log(Boolean(num));//true
console.log(Boolean(num1));//num为0时,转为false
console.log(Boolean(num1));//num为NaN时,转为false

console.log(Boolean(un));//false
console.log(Boolean(null));//false

原始值转数字类型

原始值显示转布尔类型通过调用Number()函数实现,但在内部还调用了ToNumber()方法,转换规则如下:

◎字符串转数字:空字符串转为0,若字符串中的第一个字符是找得到值的数字,则字符串中第一段数字字符会转为对应数字,其他字符会被删除;若字符串中的第一个字符不是找得到值的数字,整个字符串转换为NaN,意为找不到值

◎布尔转数字:false转为0,true转为1

◎undefined 转数字:转为NaN

◎null 转数字:转为0

let str1 = ''
let str2 = '345'

console.log(Number(str));//NaN
console.log(Number(str1));//0
console.log(Number(str2));//345

console.log(Number(false));//0

console.log(Number(un));//NaN

console.log(Number(null));//0

原始值转字符串类型

原始值显示转字符串通过调用String()实现,本质上还是调用各自的toString()方法。无论哪一种原始类型转换为字符串类型,都是转换为以自身为内容的字符串。

console.log(String(num));//所有数字转换为对应字符串,包括NaN  Infinity

console.log(String(bool));//转换为对应字符串

console.log(String(un));//转换为对应字符串

console.log(String(nu));//转换为对应字符串

原始值转引用类型

原始值转对象,都是普通对象。但值得注意的是,原始值转为对象后,仍然无法向其他普通对象一样添加属性,例如下面示例中的对象b。将示例的第一、三行代码放在浏览器上,再加上一行console.log(b),将打印结果展开,可以看到对象b的原型为Number,原始值属性[PrimitiveValue]的值为1。当计算机识别到[PrimitiveValue]属性时,就会认定该对象为包装类,将其视为原始类型对待,不允许进行属性添加操作。

let a = 1
console.log(typeof a);//number

let b = new Number(a)
console.log(typeof b);//object

对象的转换

在JavaScript中,对象可转换为任意一种原始类型。但任何对象向布尔转换得到的值都为true,因此对象的转换只讨论向数字和字符串的转换,这两种转换都通过调用Toprimitive(input,PreferrdType)方法实现。在介绍两种转换前,先清楚valueOf()、toStirng()、Toprimitive(input,PreferrdType)的调用。

Toprimitive(input,PreferrdType)
    1.input为传入的对象,PreferrdType为要转换成为的类型
    2.PreferrdType不存在,input是Date类型,相当于PreferrdType == String
    
对象调用valueOf()
    Object.prototype.valueOf()返回指定对象的原始值
    
对象调用toStirng()
    Object.prototype.toString.call()返回字符串'[object Object]'

引用类型转数字

对象向数字的转换调用Toprimitive(input,PreferrdType)方法时,第一个传入的参数为要转换类型的对象第二个传入的参数为Number,方法实现的步骤如下:

Toprimitive(obj,Number)
    1.如果obj是基本类型,直接返回
    2.否则,调用 valueOf 方法,如果得到一个原始类型,则返回
    3.否则,调用 toString 方法,如果得到一个原始类型,则返回
    4.否则,报错
    

在得到数字类型变量之前,对象会无休止地交替调用valueOf()、toStirng()。

引用类型转字符串

对象向字符串的转换调用Toprimitive(input,PreferrdType)方法时,第一个传入的参数为要转换类型的对象第二个传入的参数为String,方法实现的步骤如下:

Toprimitive(obj,String)
    1.如果obj是基本类型,直接返回
    2.否则,调用 toString 方法,如果得到一个原始类型,则返回
    3.否则,调用 valueOf 方法,如果得到一个原始类型,则返回
    4.否则,报错
    

在得到字符串型变量之前,对象会无休止地交替调用valueOf()、toStirng()。

隐式转换

当非数字类型的数据与操作符'+'连接时,会发生隐式转换。隐式转换按运算符操作变量数可分为一元操作符+、二元操作符+、'=='。 两个操作符会默认将变量向数字转换。

一元操作符+

当 + 运算作为一元操作符,后面跟着一个变量时,计算机会调用 ToNumber() 或 Toprimitive() 处理变量。

  • +运算符后跟着原始类型时,会调用ToNumber()方法,返回一个数字。例如 +'1' 会先处理字符串'1',返回数字1,console.log(+'1')会打印数字1。
  • +运算符后跟着引用类型时,会先调用Toprimitive()方法,将引用类型转换为原始类型后,再调用ToNumber()方法,返回一个数字。例如 +['1']会先转换成字符串'1',再处理字符串,返回数字1。更多示例如下:
console.log(+[]);// 0
//先调用Toprimitive(),空数组转为空字符串'';再调用ToNumber(),空字符串转为数字0

console.log(+['1']);// 1
//先调用Toprimitive(),数组转为字符串'1';再调用ToNumber(),字符串转为数字1

console.log(+['1','2','3']);// NaN
//先调用Toprimitive(),数组转为字符串'1,2,3';再调用ToNumber(),字符串转为数字NaN

console.log(+{});// NaN
//先调用Toprimitive(),数组转为字符串'[object Object]';再调用ToNumber(),字符串转为数字NaN

二元操作符 “-” 、“ *” 、“/”

当 “-” 、“ *” 、“/”这三个运算符作为二元操作符,前后连接两个变量,形如 val1 + val2 时,计算机会调用Toprimitive()分别对两个变量进行处理,将两个变量全都转换成数字类型后,进行四则运算。

console.log( '123' - '234') //得到数字结果 -111

console.log( '123' * '234') //得到数字结果 28782

console.log( '123' / '234') //得到数字结果 0.5256410256410257

二元操作符+

当 + 运算作为二元操作符,前后连接两个变量,形如 val1 + val2 时,计算机会调用Toprimitive()分别对两个变量进行处理,得到两个新变量lprim = Toprimitive(val1) rprim = Toprimitive(val2)。如果 lprim 和 rprim 中有一个是字符串,那么返回ToString(lprim)和ToString(rprim)的拼接结果,否则返回 ToNumber(lprim) 和 ToNumber(rprim) 的相加结果。

console.log(1 + '1');//'11'
//rprim为字符串,lprim调用ToString()方法,数字1转为字符串'1'与rprim拼接,得到'11'

console.log(null + 1);//1
//两个变量中有一个数字,变量默认向数字转换,null转换为0,得到相加结果数字1

console.log([]+[]);//''
//两个变量调用Toprimitive(),空数组[]转为空字符串'',拼接得到空字符串''

console.log([]+{});//'[object Object]'
//两个变量调用Toprimitive(),空数组[]转为空字符串'',空对象转为字符串'[object Object]',拼接得到'[object Object]'

console.log({} + []);//node环境下{}为字符串'[object Object]',window环境下为0
//node环境下,同上
//window环境下,V8将 {} 视为代码块,空数组转换为数字0,

console.log({}+{});//toString({})+toString({})
//两个变量调用Toprimitive(),空对象转为字符串'[object Object]',拼接得到'[object Object][object Object]'

二元操作符==

当 == 操作符连接前后两个变量时,无论两个变量的数据类型是否相同,计算机都会对两个变量是否相等作出,判断规则如下:

x==y

1.如果 x 和 y 是同一类型

  1) x 是 underfunded,返回true
  2) x 是 null,返回true
  3) x 是数字
      (1) x 是NaN,返回false,规范定义NaN不等于NaN
  4) x 和 y 指向同一个对象,返回true,否则返回false
    
   

2. null = undefined 返回true

这一项为特例,记住就好

4. 1 == 'h' 返回false

右侧变量调用ToNumble()方法,字符串'h'转为数字NaN,两侧不相等,返回false

4. false == '1' 返回false

左侧变量调用ToNumble()方法,布尔值 false 转为数字0;
右侧变量调用ToNumble()方法,字符串'1'转为数字1,两侧不相等,返回false

6. true == { a : 1 } 返回false

左侧变量调用ToNumble()方法,布尔值 true 转为数字1;
右侧变量调用ToPrimitive()方法,对象{ a : 1 }转为数字1,两侧不相等,返回false

拓展

拓展1 [] == ![]

无论是在node环境下,还是在window环境下打印 [] == ![] 的结果,得到的都是true。我第一次看见这结果时,是头脑发懵的。这究竟是为什么呢?

这一结果的出现是因为运算符优先级的存在。在运算符优先级 - JavaScript | MDN (mozilla.org)中查找可得 ! 运算符的优先级为15,== 运算符的优先级为9。因此,要把连等符右边看成一个整体,而右边先转换为布尔值false,接着又默认转换为数字0。紧接着为了比较两侧变量,计算机会将左侧的变量转为数字,空数组转换成数字0,所以两侧就相等了,判别式得到的结果就是true了。

拓展2

以下一段运算符结果可以试着探索一下,建议直接查看,不建议自己计算。

[[][0] + []][0][5]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][8]+[[[] == []][0] + []][0][2]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][6]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]]+[]][0][23]+[[][0] + []][0][3]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][8]+[+[1 + [[][0] + []][0][3] +309][0] + []][0][7]+[[][[[][0] + []][0][4]+[[][0] + []][0][5]+[[][0] + []][0][1]+[[][0] + []][0][2]] + []][0][6]+[[][0] + []][0][0]

结尾

相信仔细阅读本片文章后,JavaScript数据类型转换对你来说已经不成问题了。其他有关于JavaScript数据类型转换的问题欢迎各位读者在评论区发出讨论。如果各位在阅读文章时发现了错误也欢迎各位在评论区指正。