前言
在日常的JavaScript开发中,我们经常使用宽松相等(==)和严格相等(===)来判断两个值是否"相等",但是他们之间有一个很重要的区别,特别是在判断条件上。
那么他们到底有什么区别呢? 我想,很多人都会脱口而出:==比较值相等,===会判断值和类型是否相等。听起来还是挺有道理的,但是还是不够准确。
正确的解释是:==允许在判断相等时进行强制类型转换,而===不允许。
但是你了解==在判断是进行强制类型转换的具体规则吗?在ES5规范中提到了 “抽象相等比较算法”,定义了==运算符的行为。
1. 比较规则
1.1 类型相同之间的相等比较
当==左右两侧的数据类型相同时,直接比较值是否相等。NaN除外,因为NaN是唯一一个自反的数据。
1.2 字符串和数字之间的相等比较
我们现在来举个例子:
var a = '42'
var b = 42
console.log(a == b) // true
console.log(a === b) // false
想必大家都知道==会输出为true,而=== 会输出为false。因为==发生了强制类型转换,但是你知道是a强制转换成了数字,还是强制类型转换成了字符串呢??
ES5规范中这样定义:
- 如果type(x)是数字,type(y)是字符串,则返回x==toNumber(y)的结果。
- 如果type(x)是字符串,type(y)是数字,则返回toNumber(x)==y的结果。
所以根据规范,上述例子应该将a的值'42'转换为数字42进行比较。
1.3 其他值与布尔值之间的相等比较
==最容易出错的就是false和true与其他类型值进行比较。
例如:
var a = '42'
var b = true
console.log(a==b) // false
我们都知道'42'为真值,但是为什么会为false呢?
在ES5规范中是这样说的:
- 如果type(y)是布尔值,则返回x==toNumber(y)的结果。
- 如果type(x)是布尔值,则返回toNumber(x)==y的结果。
所以上诉例子应该将b转换为数字1,此时为字符串和数字的相对比较,根据上述1.2中的规范,将字符串'42'转换为数字42,42与1不等,所以结果为false。
当然,反过来也一样:
var a = true
var b = '42'
console.log(a == b) // false
看到这里,你是否会有一个疑问:为什么"42"既不等于true也不等false?
“42”本身是一个真值(true)没错,其实这个问题本身就是错的。因为这里比较时,不是将"42"转换成布尔值,而是将布尔值转换为对应的数字表示(0或1),所以最后的结果才会是false。
1.3 null与undefined之间的相等比较
在ES5规范中这样说道:
- 若x为null,y为undefined,则x==y为true。
- 若x为undefined,y为null,则x==y为true。
var a = null
var b
console.log(a == b) // true
console.log(a == null) // true
console.log(b == null) // true
console.log(a == undefined) // true
console.log(b == undefined) // true
console.log(a == '') // false
console.log(b == '') // false
console.log(b == false) // false
console.log(b == false) // false
也就是说在==中,null与undefined是一回事。
1.4 对象和非对象之间的相等比较
在ES5中做出如下规定:
- 如果type(x)为字符串或数字,type(y)是对象,则返回x == ToPrimitive(y)的结果
- 如果type(x)为对象,type(y)是字符串或数字,则返回ToPrimitive(x) == y的结果
这里没有提到布尔值,因为遇到布尔值首先会将布尔值转换为数字。
var a = '42'
var b = [42]
console.log(a == b) // true
这里首先b调用ToPrimitive,返回42,然后将'42'转换为数字42,最终为42==42,结果为true。
这里需要注意一下几个情况:
var a = null
var b = Object(a)
console.log(a == b) // false
var c = undefined
var d = Object(c)
console.log(c == d) // false
var e = NaN
var f = Object(e)
console.log(e == f) // false
因为没有对应的封装对象,所以null与undefined无法被封装,Object(null)与Object()均返回一个常规对象。
NaN能够被数字对象封装,而NaN为自反数据,所以拆分之后,返回false。
当然也会有几种特殊的请求,例如:
console.log(0 == '') // true
console.log(0 == []) // true
console.log([] == '') // true
宽松相等如果使用不当,会给我们带来很多烦恼,所以使用宽松相等要遵守以下两个原则,可以为我们避免不必要的错误:
- 如果两边的值有true或false,千万不要使用==。
- 如果两边的值有[ ]、“”、0,尽量不要使用==。
1.5 抽象关系比较
抽象关系比较中的隐式强制类型转换也许不太引人注目,不过要想成为优秀的JavaScript开发者,应该深入了解一下。
在ES5规范中这样定义:抽象关系比较分为两个部分,比较双方都是字符串和其他情况。
1.5.1 其他情况
比较双方先调用ToPromitive,如果结果出现非字符串,则调用ToNumber,将双方强制转换为数字进行比较。
例如:
var a = [42]
var b = ['43']
console.log(a < b) // true
如果比较的双方都是字符串,则按字母顺序来进行比较
var a = ['42']
var b = ['043']
console.log(a < b) // false
ToPromitive返回的是字符串,a和b并没有转换成数字,所以比较的是‘42’字符串和‘043’字符串,因为‘0’在‘4’的前面,所以返回false。
那么这个例子会返回什么呢?
var a = { b: '42' }
var b = { b: '043' }
console.log(a < b) // ???
还是false,因为a为[object Object],b也是[object Object],所以a < b 并不成立。
这里要注意了,神奇的事情要发生了,请看下面这个例子:
var a = { b: '42' }
var b = { b: '043' }
console.log(a < b) // false
console.log(a > b) // false
console.log(a == b) // false
console.log(a <= b) // true
console.log(a >= b) // true
为什么a==b 和 a< b同为false,而a <=b 和a >=b 就为true呢?
因为根据ES5规范,a<=b 被处理为b <a ,然后将结果反转,因为b< a 的结果为false,所以结果为true。
这里为什么a ==b 为false呢? 难道不是同为[object Object]吗?
这里应该按照上面所说的类型相等来判断,如果同为对象,则会判断对象所指向的值是否相等,不发生强制类型转换。
最后
现在我们就已经了解了宽松相等中的一些“坑”,我相信大家在以后的开发中一定不会入这些‘坑’。
现在你是否会有一个疑问:什么时候该用“==”,什么时候用“===”
在《你不知道的JavaScript(中)》一书中提到:
- 如果比较两个类型相同的值,则== 和=== 会使用相同的算法,所以除了JavaScript引擎的一些细微区别,他们之间没有任何不同。
- 如果比较两个类型不同的值,我们就需要考虑有没有强制类型转换的比较,如果有则使用==,如果没有则使用===,不用在乎性能。