“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)。
一般来说,作为代码书写规范来说,推荐常量在左进行判断的写法。