前方提醒: 篇幅较长,点个赞或者收藏一下,能够在下一次阅读时方便查找javascript
提到js的隐式转换,不少人第一反应都是:坑。java
的确,对于不熟悉的人来讲,js隐式转换
存在着不少的让人没法预测的地方,相信不少人都深受其害,因此,你们在开发过程当中,可能会使用===
来尽可能避免隐式转换。可是,为了更加深刻的理解javascript
,本着对知识渴望的精神,咱们来经过大量的例子分析分析js隐式转换
,熟悉js隐式转换
的规则,让其在你的眼里变成“显式”。面试
先来看看一个经典的面试题数组
定义一个变量
a
,使得下面的表达式结果为true
ui
a == 1 && a == 2 && a == 3
复制代码
还有这种操做?先试试看吧,定义a = true
?this
var a = true
a == 1 && a == 2 && a == 3 // false
复制代码
可是并无达到预期,好像触碰到知识盲区了。。。不要紧,先放下吧,来看看几个更坑的spa
[] == ![] // true
[] == 0 // true
[2] == 2 // true
['0'] == false // true
'0' == false // true
[] == false // true
[null] == 0 // true
null == 0 // false
[null] == false // true
null == false // false
[undefined] == false // true
undefined == false // false
复制代码
一脸懵逼? 没关系!接下来带你完彻底全的认识javascript的隐式转换
。prototype
咱们须要先了解一下js数据类型之间转换的基本规则,好比数字、字符串、布尔型、数组、对象之间的相互转换。code
这里所说的
ToString
可不是对象的toString方法
,而是指其余类型的值转换为字符串类型的操做。对象
这里咱们讨论null
、undefined
、布尔型
、数字
、数组
、普通对象
转换为字符串的规则。
"null"
"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]"
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(10) // '10'
String(1e21) // '1e+21'
String([1,2,3]) // '1,2,3'
String([]) // ''
String([null]) // ''
String([1, undefined, 3]) // '1,,3'
String({}) // '[object Objecr]'
复制代码
对象的toString
方法,知足ToString
操做的规则。
注意:上面所说的规则是在默认的状况下,若是修改默认的
toString()
方法,会致使不一样的结果
ToNumber
指其余类型转换为数字类型的操做。
0
NaN
0
, 不然一概按转换失败处理,转为NaN
true
和false
被转为1
和0
ToPrimitive
,而后在根据转换后的原始类型按照上面的规则处理,关于ToPrimitive
,会在下文中讲到Number(null) // 0
Number(undefined) // NaN
Number('10') // 10
Number('10a') // NaN
Number('') // 0
Number(true) // 1
Number(false) // 0
Number([]) // 0
Number(['1']) // 1
Number({}) // NaN
复制代码
ToBoolean
指其余类型转换为布尔类型的操做。
js中的假值只有false
、null
、undefined
、空字符
、0
和NaN
,其它值转为布尔型都为true
。
Boolean(null) // false
Boolean(undefined) // false
Boolean('') // flase
Boolean(NaN) // flase
Boolean(0) // flase
Boolean([]) // true
Boolean({}) // true
Boolean(Infinity) // true
复制代码
ToPrimitive
指对象类型类型(如:对象、数组)转换为原始类型的操做。
valueOf
方法,若是valueOf
方法返回原始类型的值,则ToPrimitive
的结果就是这个值valueOf
不存在或者valueOf
方法返回的不是原始类型的值,就会尝试调用对象的toString
方法,也就是会遵循对象的ToString
规则,而后使用toString
的返回值做为ToPrimitive
的结果。注意:对于不一样类型的对象来讲,
ToPrimitive
的规则有所不一样,好比Date对象
会先调用toString
,具体能够参考ECMA标准
若是valueOf
和toString
都没有返回原始类型的值,则会抛出异常。
Number([]) // 0
Number(['10']) //10
const obj1 = {
valueOf () {
return 100
},
toString () {
return 101
}
}
Number(obj1) // 100
const obj2 = {
toString () {
return 102
}
}
Number(obj2) // 102
const obj3 = {
toString () {
return {}
}
}
Number(obj3) // TypeError
复制代码
前面说过,对象类型在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
,因此会抛出错误看到这里,觉得本身彻底掌握了?别忘了,那道面试题和那一堆让人懵逼的判断还没解决呢,本着对知识渴望的精神,继续往下看吧。
宽松相等(==)
和严格相等(===)
的区别在于宽松相等会在比较中进行隐式转换
。如今咱们来看看不一样状况下的转换规则。
布尔类型
参与比较,该布尔类型
的值首先会被转换为数字类型
布尔类型
的ToNumber
规则,true
转为1
,false
转为0
false == 0 // true
true == 1 // true
true == 2 // false
复制代码
以前有的人可能以为数字
2
是一个真值,因此true == 2
应该为真,如今明白了,布尔类型true
参与相等比较会先转为数字1
,至关于1 == 2
,结果固然是false
咱们平时在使用if
判断时,通常都是这样写
const x = 10
if (x) {
console.log(x)
}
复制代码
这里if(x)
的x
会在这里被转换为布尔类型,因此代码能够正常执行。可是若是写成这样:
const x = 10
if (x == true) {
console.log(x)
}
复制代码
代码不会按照预期执行,由于x == true
至关于10 == 1
数字类型
和字符串类型
作相等比较时,字符串类型
会被转换为数字类型
ToNumber
规则,若是是纯数字形式的字符串,则转为对应的数字,空字符转为0
, 不然一概按转换失败处理,转为NaN
0 == '' // true
1 == '1' // true
1e21 == '1e21' // true
Infinity == 'Infinity' // true
true == '1' // true
false == '0' // true
false == '' // true
复制代码
上面比较的结果和你预期的一致吗? 根据规则,字符串转为数字,布尔型也转为数字,因此结果就显而易见了。
这里就不讨论
NaN
了,由于NaN
和任何值都不相等,包括它本身。
对象类型
和原始类型
作相等比较时,对象类型
会依照ToPrimitive
规则转换为原始类型
'[object Object]' == {} // true
'1,2,3' == [1, 2, 3] // true
复制代码
看一下文章开始时给出的例子
[2] == 2 // true
复制代码
数组[2]
是对象类型,因此会进行ToPrimitive
操做,也就是先调用valueOf
再调用toString
,根据数组ToString
操做规则,会获得结果"2"
, 而字符串"2"
再和数字2
比较时,会先转为数字类型,因此最后获得的结果为true
。
[null] == 0 // true
[undefined] == 0 // true
[] == 0 // true
复制代码
根据上文中提到的数组ToString
操做规则,数组元素为null
或undefined
时,该元素被当作空字符串
处理,而空数组[]
也被转为空字符串
,因此上述代码至关于
'' == 0 // true
'' == 0 // true
'' == 0 // true
复制代码
空字符串
会转换为数字0
,因此结果为true
。
试试valueOf方法
const a = {
valueOf () {
return 10
}
toString () {
return 20
}
}
a == 10 // true
复制代码
对象的
ToPrimitive
操做会先调用valueOf
方法,而且a
的valueOf
方法返回一个原始类型的值,因此ToPrimitive
的操做结果就是valueOf
方法的返回值10
。
讲到这里,你是否是想到了最开始的面试题? 对象每次和原始类型作==
比较时,都会进行一次ToPrimitive
操做,那咱们是否是能够定义一个包含valueOf
方法的对象,而后经过某个值的累加来实现?
试一试
const a = {
// 定义一个属性来作累加
i: 1,
valueOf () {
return this.i++
}
}
a == 1 && a == 2 && a == 3 // true
复制代码
结果正如你所想的,是正确的。固然,当没有定义valueOf
方法时,用toString
方法也是能够的。
const a = {
// 定义一个属性来作累加
i: 1,
toString () {
return this.i++
}
}
a == 1 && a == 2 && a == 3 // true
复制代码
null
和undefined
宽松相等的结果为true,这一点你们都知道其次,null
和undefined
都是假值,那么
null == false // false
undefined == false // false
复制代码
竟然跟我想的不同?为何呢? 首先,false
转为0
,而后呢? 没有而后了,ECMAScript规范
中规定null
和undefined
之间互相宽松相等(==)
,而且也与其自身相等,但和其余全部的值都不宽松相等(==)
。
如今再看前面的这一段代码就明了了许多
[] == ![] // true
[] == 0 // true
[2] == 2 // true
['0'] == false // true
'0' == false // true
[] == false // true
[null] == 0 // true
null == 0 // false
[null] == false // true
null == false // false
[undefined] == false // true
undefined == false // false
复制代码
最后想告诉你们,不要一味的排斥javascript的隐式转换,应该学会如何去利用它,你的代码中可能存在着不少的隐式转换,只是你忽略了它,要作到知其然,并知其因此然,这样才能有助于咱们深刻的理解javascript。
(看了这么久了,辛苦了,不过我也写了好久啊,点个赞再走吧)