javascript隐式转换规则
1. ToString,ToNumber,ToBoolean,ToPrimitive
我们需要先了解一下js数据类型之间转换的基本规则,比如数字、字符串、布尔型、数组、对象之间的相互转换。
1.1 ToString
这里所说的
ToString
可不是对象的toString方法
,而是指其他类型的值转换为字符串类型的操作。
这里我们讨论null
、undefined
、布尔型
、数字
、数组
、普通对象
转换为字符串的规则。
- null:转为
"null"
- undefined:转为
"undefined"
- 布尔类型:
true
和false
分别被转为"true"
和"false"
- 数字类型:转为数字的字符串形式,如
10
转为"10"
,1e21
转为"1e+21"
- 数组:转为字符串是将所有元素按照”,”连接起来,相当于调用数组的
Array.prototype.join()
方法[1, 2, 3]
转为"1,2,3"
- 空数组
[]
转为空字符串 - 数组中的
null
或undefined
,会被当做空字符串处理
- 普通对象:转为字符串相当于直接使用
Object.prototype.toString()
,返回"[object Object]"
1 | String(null) // 'null' |
对象的toString
方法,满足ToString
操作的规则。
注意:上面所说的规则是在默认的情况下,如果修改默认的
toString()
方法,会导致不同的结果
1.2 ToNumber
ToNumber
指其他类型转换为数字类型的操作。
- null: 转为
0
- undefined:转为
NaN
- 字符串:如果是纯数字形式,则转为对应的数字,空字符转为
0
, 否则一律按转换失败处理,转为NaN
- 布尔型:
true
和false
被转为1
和0
- 数组:数组首先会被转为原始类型,也就是
ToPrimitive
,然后在根据转换后的原始类型按照上面的规则处理,关于ToPrimitive
,会在下文中讲到 - 对象:同数组的处理
1 | Number(null) // 0 |
1.3 ToBoolean
ToBoolean
指其他类型转换为布尔类型的操作。
js中的假值只有false
、null
、undefined
、空字符
、0
和NaN
,其它值转为布尔型都为true
。
1 | Boolean(null) // false |
1.4 ToPrimitive
ToPrimitive
指对象类型类型(如:对象、数组)转换为原始类型的操作。
- 当对象类型需要被转为原始类型时,它会先查找对象的
valueOf
方法,如果valueOf
方法返回原始类型的值,则ToPrimitive
的结果就是这个值 - 如果
valueOf
不存在或者valueOf
方法返回的不是原始类型的值,就会尝试调用对象的toString
方法,也就是会遵循对象的ToString
规则,然后使用toString
的返回值作为ToPrimitive
的结果。
如果valueOf
和toString
都没有返回原始类型的值,则会抛出异常。
1 | Number([]) // 0 |
前面说过,对象类型在ToNumber
时会先ToPrimitive
,再根据转换后的原始类型ToNumber
Number([])
, 空数组会先调用valueOf
,但返回的是数组本身,不是原始类型,所以会继续调用toString
,得到空字符串
,相当于Number('')
,所以转换后的结果为"0"
同理,
Number(['10'])
相当于Number('10')
,得到结果10
obj1
的valueOf
方法返回原始类型100
,所以ToPrimitive
的结果为100
obj2
没有valueOf
,但存在toString
,并且返回一个原始类型,所以Number(obj2)
结果为102
obj3
的toString
方法返回的不是一个原始类型,无法ToPrimitive
,所以会抛出错误
2. 宽松相等(==)比较时的隐式转换规则
宽松相等(==)
和严格相等(===)
的区别在于宽松相等会在比较中进行隐式转换
。现在我们来看看不同情况下的转换规则。
2.1 布尔类型和其他类型的相等比较
- 只要
布尔类型
参与比较,该布尔类型
的值首先会被转换为数字类型
- 根据
布尔类型
的ToNumber
规则,true
转为1
,false
转为0
1 | false == 0 // true |
之前有的人可能觉得数字
2
是一个真值,所以true == 2
应该为真,现在明白了,布尔类型true
参与相等比较会先转为数字1
,相当于1 == 2
,结果当然是false
我们平时在使用if
判断时,一般都是这样写
1 | const x = 10 |
这里if(x)
的x
会在这里被转换为布尔类型,所以代码可以正常执行。但是如果写成这样:
1 | const x = 10 |
代码不会按照预期执行,因为x == true
相当于10 == 1
2.2 数字类型和字符串类型的相等比较
- 当
数字类型
和字符串类型
做相等比较时,字符串类型
会被转换为数字类型
- 根据字符串的
ToNumber
规则,如果是纯数字形式的字符串,则转为对应的数字,空字符转为0
, 否则一律按转换失败处理,转为NaN
1 | 0 == '' // true |
上面比较的结果和你预期的一致吗? 根据规则,字符串转为数字,布尔型也转为数字,所以结果就显而易见了。
这里就不讨论
NaN
了,因为NaN
和任何值都不相等,包括它自己。
2.3 对象类型和原始类型的相等比较
- 当
对象类型
和原始类型
做相等比较时,对象类型
会依照ToPrimitive
规则转换为原始类型
1 | '[object Object]' == {} // true |
看一下文章开始时给出的例子
1 | [2] == 2 // true |
数组[2]
是对象类型,所以会进行ToPrimitive
操作,也就是先调用valueOf
再调用toString
,根据数组ToString
操作规则,会得到结果"2"
, 而字符串"2"
再和数字2
比较时,会先转为数字类型,所以最后得到的结果为true
。
1 | [null] == 0 // true |
根据上文中提到的数组ToString
操作规则,数组元素为null
或undefined
时,该元素被当做空字符串
处理,而空数组[]
也被转为空字符串
,所以上述代码相当于
1 | '' == 0 // true |
空字符串
会转换为数字0
,所以结果为true
。
试试valueOf方法
1 | const a = { |
对象的
ToPrimitive
操作会先调用valueOf
方法,并且a
的valueOf
方法返回一个原始类型的值,所以ToPrimitive
的操作结果就是valueOf
方法的返回值10
。
对象每次和原始类型做==
比较时,都会进行一次ToPrimitive
操作,那我们是不是可以定义一个包含valueOf
方法的对象,然后通过某个值的累加来实现?
试一试
1 | const a = { |
结果正如你所想的,是正确的。当然,当没有定义valueOf
方法时,用toString
方法也是可以的。
1 | const a = { |
2.4 null、undefined和其他类型的比较
null
和undefined
宽松相等的结果为true,这一点大家都知道
其次,null
和undefined
都是假值,那么
1 | null == false // false |
居然跟我想的不一样?为什么呢? 首先,false
转为0
,然后呢? 没有然后了,ECMAScript规范
中规定null
和undefined
之间互相宽松相等(==)
,并且也与其自身相等,但和其他所有的值都不宽松相等(==)
。
最后
现在这一段代码就明了了许多
1 | [] == ![] // true |