JavaScript中的两个等号(==)和三个等号(===)

September 22, 2013
author: feeldesignstudio.com

“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:(判断步骤如下,注意次序

  1. If Type(x) is different from Type(y), return false.
  2. If Type(x) is Undefined, return true.
  3. If Type(x) is Null, return true.
  4. 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.
  5. 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.
  6. If Type(x) is Boolean, return true if x and y are both true or both false; otherwise, return false.
  7. 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:(判断步骤如下,注意次序

  1. 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).
  2. 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).
  3. 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.
  4. 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

分析如下的代码:

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),不了解规则时,会发现表现十分的奇怪:

'' == '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。所以建议除非非常清楚自己想要的,一般尽量使用 === 来进行比较。

下面介绍与判断相等相关的几个知识:

!! 运算符

注意下面的代码:

NaN   ===  NaN     //false
!!NaN === !!NaN    //true

我们经常会在代码中看到 !! 运算符,它是一个编程技巧,作用主要是把一个变量或表达式转换为boolean,看下面的代码(均返回 true):

!!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资料时可能会发现有这样的写法推荐,即变量放在双等号的右边,常量放在左边:

//尽量使用
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)。

一般来说,作为代码书写规范来说,推荐常量在左进行判断的写法。

Tips: 评论也可以匿名哦,不一定注册的,只需要勾选匿名评论(I'd rather post as a guest)即可