“Determining whether two variables are equivalent is one of the most important operations in programming.” (确定两个变量是否相等是编程中最重要的操作之一)
——Nicholas Zakas
JavaScript中作比较有两个方式:严格模式(strict comparison 使用三个等号 ===)和概要模式(abstract comparison 使用两个等号 ==),对于他们的意义和行为,本文做一个归纳。
为了避免舍本逐末,学习任何知识应该先从标准的定义开始,下面是这两种比较方式在ECMA262标准中的定义:
严格模式 ===
The Strict Equality Comparison Algorithm(严格相等比较算法)
The comparison x === y, where x and y are values, produces true or false. Such a comparison is performed as follows:(判断步骤如下,注意次序)
- If Type(x) is different from Type(y), return false.
- If Type(x) is Undefined, return true.
- If Type(x) is Null, return true.
- If Type(x) is Number, then
- If x is NaN, return false.
- If y is NaN, return false.
- If x is the same Number value as y, return true.
- If x is +0 and y is −0, return true.
- If x is −0 and y is +0, return true.
- Return false.
- If Type(x) is String, then return true if x and y are exactly the same sequence of characters (same length and same characters in corresponding positions); otherwise, return false.
- If Type(x) is Boolean, return true if x and y are both true or both false; otherwise, return false.
- Return true if x and y refer to the same object. Otherwise, return false.
概要模式 ==
The Abstract Relational Comparison Algorithm
The comparison x < y, where x and y are values, produces true, false, or undefined (which indicates that at least one operand is NaN). In addition to x and y the algorithm takes a Boolean flag named LeftFirst as a parameter. The flag is used to control the order in which operations with potentially visible side-effects are performed upon x and y. It is necessary because ECMAScript specifies left to right evaluation of expressions. The default value of LeftFirst is true and indicates that the x parameter corresponds to an expression that occurs to the left of the y parameter’s corresponding expression. If LeftFirst is false, the reverse is the case and operations must be performed upon y before x. Such a comparison is performed as follows:(判断步骤如下,注意次序)
- If the LeftFirst flag is true, then
- Let px be the result of calling ToPrimitive(x, hint Number).
- Let py be the result of calling ToPrimitive(y, hint Number).
- Else the order of evaluation needs to be reversed to preserve left to right evaluation
- Let py be the result of calling ToPrimitive(y, hint Number).
- Let px be the result of calling ToPrimitive(x, hint Number).
- If it is not the case that both Type(px) is String and Type(py) is String, then
- Let nx be the result of calling ToNumber(px). Because px and py are primitive values evaluation order is not important.
- Let ny be the result of calling ToNumber(py).
- If nx is NaN, return undefined.
- If ny is NaN, return undefined.
- If nx and ny are the same Number value, return false.
- If nx is +0 and ny is −0, return false.
- If nx is −0 and ny is +0, return false.
- If nx is +∞, return false.
- If ny is +∞, return true.
- If ny is −∞, return false.
- If nx is −∞, return true.
- If the mathematical value of nx is less than the mathematical value of ny —note that these mathematical values are both finite and not both zero—return true. Otherwise, return false.
- Else, both px and py are Strings
- If py is a prefix of px, return false. (A String value p is a prefix of String value q if q can be the result of concatenating p and some other String r. Note that any String is a prefix of itself, because r may be the empty String.)
- If px is a prefix of py, return true.
- Let k be the smallest nonnegative integer such that the character at position k within px is different from the character at position k within py. (There must be such a k, for neither String is a prefix of the other.)
- Let m be the integer that is the code unit value for the character at position k within px.
- Let n be the integer that is the code unit value for the character at position k within py.
- If m < n, return true. Otherwise, return false.
通过定义,可以归纳出下面的特点:
-
- === 不做类型转换,类型不同的一定不等,返回false;
- 两个string严格相等表示它们有相同的字符排列、相同的长度和每个位置的字符都相同;
- 两个number严格相等表示它们有相同的数值,NaN和任何东西都不相等,包括NaN它自己;正负零彼此之间相等;
- 两个boolean严格相等表示它们同时为true或者同时为false;
- 两个不同的object在严格和概要比较中都不相等,返回false;
- 两个object相等唯一的情况是他们引用了相同的object;
- == 在两边值类型不同的时候,会做如下的转换再严格比较:
- null == undefined 但是 null !== undefined;
- 如果有一个操作数是一个数字或布尔值,如果可能,另一个操作数转换为数字;否则,如果其中一个操作数为字符串,如果可能,另一个操作数被转换为字符串;
- 如果两个操作数都是对象,那会比较对象在内存中的引用是否相同。
根据上面的规则,我们知道:如果在比较时两个变量的类型很重要,就要使用严格比较(===);否则可以使用一般比较(==)。
在JavaScript中,下面的值被当做假(false),除了下面列出的值,都被当做真(true):
- false
- null
- undefined
- 空字符串 ”
- 数字 0
- NaN
分析如下的代码:
1
2
3
4
5
6
7
8
9
10
11
12
|
5
==
'5'
// true 右边的字符串会被转换为数字,然后进行比较
"1"
==
true
// true true会先转换成数值 1,然后进行比较
//注意 String 对象和 string字符串不同,String对象一般来说很少使用
var
a
=
new
String
(
"foo"
)
;
var
b
=
new
String
(
"foo"
)
;
a
==
b
//false
a
===
b
//false
a
==
"foo"
//true
a
===
"foo"
//false
|
注意在使用 == 时,在类型不同时,会进行强制类型转换,这个转换的规则十分复杂(上面的特点中有简单的说明,详见 ToPrimitive),不了解规则时,会发现表现十分的奇怪:
1
2
3
4
5
6
7
8
9
10
11
|
''
==
'0'
//false
0
==
''
//true
0
==
'0'
//true
false
==
'false'
//false
false
==
'0'
//true
false
==
undefined
//false
false
==
null
//false
null
==
undefined
//true
' \t\r\n '
==
0
//true
|
通过上面的例子可以看出,== 在传递性(即 a == b, a == c 推出 b == c)上是不可靠的,以上例子中如果使用 ===,结果都会是 false。所以建议除非非常清楚自己想要的,一般尽量使用 === 来进行比较。
下面介绍与判断相等相关的几个知识:
!! 运算符
注意下面的代码:
1
2
|
NaN
===
NaN
//false
!
!
NaN
===
!
!
NaN
//true
|
我们经常会在代码中看到 !! 运算符,它是一个编程技巧,作用主要是把一个变量或表达式转换为boolean,看下面的代码(均返回 true):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
!
!
false
===
false
!
!
true
===
true
!
!
0
===
false
!
!
parseInt
(
"foo"
)
===
false
// NaN 为 false
!
!
1
===
true
!
!
-
1
===
true
!
!
""
===
false
//空字符串为false
!
!
"foo"
===
true
//非空字符串都为true
!
!
"false"
===
true
//非空字符串都为true
!
!
window
.
foo
===
false
//undefined为false
!
!
null
===
false
//null为false
!
!
{
}
===
true
//空对象为true
!
!
[
]
===
true
//空数组为true
!
!
new
Boolean
(
false
)
===
true
//这里是个对象
!
!
Boolean
(
false
)
===
false
//这里才是boolean值
|
相类似的快速转换类型写法还有下面的两个:
Number(foo) === +foo
String(foo) === ”+foo
变量和常量的顺序
大家在阅读js资料时可能会发现有这样的写法推荐,即变量放在双等号的右边,常量放在左边:
1
2
3
4
5
|
//尽量使用
if
(
'0'
==
a
)
{
.
.
.
.
.
.
//不要使用
if
(
a
==
'0'
)
{
.
.
.
.
.
.
|
这是“Yoda表示法”,名字来源于《星球大战》的 Yoda 大师。他说话的单词顺序相当奇特,比如:“Backwards it is, yes!”。
这样写主要是防止缺少等号的笔误,比如把 if ( a == ’0′ ) 误写成了 if ( a = ’0′ ),如果采用了常量在前的判断写法,如果把 if ( ’0′ == a ) 误写成了 if ( ’0′ = a ),则会抛出错误(ReferenceError: Invalid left-hand side in assignment)。
一般来说,作为代码书写规范来说,推荐常量在左进行判断的写法。