在刚刚开始学习js时,有时会碰见这些情况:
这些转换看着就让人头大,不禁感叹JS的类型转换也太灵活多变了,这都归结于JS作为一个动态弱类型语言的特性,它在好用的同时也带来很多麻烦。本文将详细地分析JS的类型转换到底是如何运行的。
首先我们知道js中基本类型有:undefined / null / number / string / boolean / bigint / symbol,基本类型数据放在栈内存中基本类型数据是不可变的。引用类型有:Object / Array / Function 等,本质上都是属于 Object,引用类型以:地址: 数据 的映射关系来进行存储,其中地址放在栈内存,数据放在堆内存,若两个或 N 个变量指向同一个地址,则共用一份数据。引用类型数据是可变的。原始数据类型为:number、string、boolean、null、undefined、object null
类型转换
1.显示类型转换
- 原始值转布尔
Boolean()
- 原始值转数字
Number()
//ToNumber()是Number类型里的一个方法
parseInt(3.14, 2)
// 将3.14看成2进制数,转出十进制
- 原始值转字符串
String()
使用String()函数转换时,对Number和Boolean类型实际就是调用ToString()方法,但是对于null和undefined就会直接转换为'null','undefined'
- 原始值转对象
let a = 1
let b = new Number(a)//b是Number对象
let c = new String('2')//c时String对象
let d = new Boolean(false)//d是Boolean对象
- 对象转布尔
Boolean(new Boolean(false))
- 对象转数字和字符串
toString()
valueOf() //返回对象中的[[PrimitiveValue]]原始值
Object.prototype.toString.call()
//Object.prototype上的原生toString()方法判断数据类型
Number(obj)
String(obj)//将引用类型转换为基本类型
在String({})和 Number({})调用中,会执行
ToPrimitive(input, PreferredType)
它把对象转为原始类型,如果PreferredType不存在,且input是Date类型,相当于PreferredType == String
当调用Number(obj)时,执行ToPrimitive(obj, Number),执行过程为:
- 如果obj是基本类型,直接返回
- 否则,调用 valueOf 方法,如果得到一个原始类型,则返回
- 否则,调用 toString 方法,如果得到一个原始类型,则返回
- 否则报错
当调用String(obj)时,执行ToPrimitive(obj, String),执行过程为: ToPrimitive(obj, String)
- 如果obj是基本类型,直接返回
- 否则,调用 toString 方法,如果得到一个原始类型,则返回
- 否则,调用 valueOf 方法,如果得到一个原始类型,则返回
- 否则报错
在这里附上一张类型转换表:
| 原始值 | 转换为数字 | 转换为字符串 | 转换为布尔值 |
|---|---|---|---|
| false | 0 | 'false' | false |
| false | 0 | 'false' | false |
| true | 1 | 'true' | true |
| 0 | 0 | '0' | false |
| 1 | 1 | '1' | true |
| '0' | 0 | '0' | true |
| '000' | 0 | '000' | true |
| '1' | 1 | '1' | true |
| NaN | NaN | 'NaN' | false |
| Infinity | Infinity | 'Infinity' | true |
| -Infinity | -Infinity | '-Infinity' | true |
| '' | 0 | '' | false |
| '20' | 20 | '20' | true |
| 'endless' | NaN | 'endless' | true |
| [ ] | 0 | '' | true |
| [20] | 20 | '20' | true |
| [10,20] | NaN | '10,20' | true |
| ['endless'] | NaN | 'endless' | true |
| ['end','less'] | NaN | 'endless,endless' | true |
| function(){} | NaN | 'function(){}' | true |
| function(){} | NaN | 'function(){}' | true |
| { } | NaN | '[object Object]' | true |
| null | 0 | 'null' | false |
| undefined | NaN | 'undefined' | false |
接下来我们来看看在隐示类型转换中,这些方法具体是如何调用的。
2.隐示类型转换
1)一元运算符
+'1' Number('1')
一元运算符 + 在这里相当于调用了Number(),其中调用了ToPrimitive(obj, Number),因为'1'是基本类型,所以直接返回,最后返回数字1。
+[]
同理,+[]相当于先调用了Number(),其中调用了ToPrimitive(obj, Number),在这里因为[]不是基本类型也没有原始值,所以调用toString方法得到空字符串'',最后返回数字0
2)二元运算符
在二元运算符运算时,遵循以下规则
v1 + v2
- lprim = ToPrimitive(v1)
- rprim = ToPrimitive(v2)
- 如果 lprim 是字符串或 rprim 是字符串,那么返回 ToString(lprim) 和 ToString(rprim)的拼接结果
- 否则返回 ToNumber(lprim) 和 ToNumber(rprim)的相加结果
举几个例子:
1 + '1'
//'1'+'1'
//'11'
//
在这里执行ToPrimitive(1)和ToPrimitive('1')
那么两边都转成了'1'
然后将两边拼接得到'11'
[] + {}
//'' + '[object, object]'
//'[object, object]'
//
同理,先执行ToPrimitive([])和ToPrimitive({})
得到'' 和 '[object, object]'
然后将它们拼接得到结果'[object, object]'
不过在这里有一种特殊情况就是:
{} + []
//+[]
//0
在识别的时候会将{}识别成代码块,所以在这里变成了+[],所以最后得出的结果为0。
3)==
x == y
遵从以下规律
1.如果 x 和 y 是同一类型,那么
-
如果x 是 Undefined,返回true
-
如果 x 是 null,返回true
-
如果 x 是数字,那么
- 如果 x 是 NaN ,返回 false
-
如果x和y指向同一个对象,返回true,否则返回false
2 如果x 和 y 不是同一类型,那么先将两边转为原始类型,然后再转换为数字
接下来上几个例子
1=='h'
//ToNumber('h')
//false
在这里两边都是原始类型,而且1是数字,所以执行ToNumber('h'),得到右边是0,最后输出结果为false ``
true=={a:1}
// true =='[object Object]'
// 1==NaN
// false
//
//
//
在这里会执行ToPrimitive({a: 1}),得到'[object Object]',然后执行ToNumber('[object Object]'),右边为NaN,所以输出false
[] == ![]
// []== false
// ''==0
//
//
在这里因为!的优先级大于==,所以先执行![],将其进行强制boolean转换为false。 左边转换为''之后再转换为0,所以最后得到0 == 0,输出true