JavaScript tricks 2: Use === instead of ==

本文是关于JavaScript的陷阱和最佳实践中的一部分。

尽量使用===而不是==

=== && ==

JavaScript中,有二种判断值是否相等的等性运算符:

严格判等运算符===

ES5 11.9.6描述了===的具体算法。我们以x === y为例:

  1. 首先,如果xy的类型不同,返回false
  2. 如果类型为UndefinedNull,返回true
  3. 如果类型是Number
    1. 如果xyNaN,返回false
    2. 如果xy的数值相等,返回true
    3. 如果x+0y-0,返回true,反之亦然
    4. 其它情况返回false
  4. 如果类型为String,那么很明显,必须两者包含相同的字符串才会返回true,否则返回false
  5. 如果类型为Number,与String类型情况一样,必须两者同时为truefalse才会返回true,否则返回false
  6. 如果xy两者都指向同一个对象时,返回true,否则返回false
NaN === NaN // false
+0 === -0 // true

var foo = function() {}
foo === foo // true

注意:NaN并不等于它自己,即NaN === NaN返回false。所以我们必须使用其它方法来判断某个值是否为NaN

普通判等运算符==

ES5 11.9.3描述了==的具体算法。我们以x == y为例:

  1. 如果xy的类型相同,则使用===算法比对
  2. 如果xnullyundefined,则返回true,反之亦然
  3. 如果其中一个是Number类型,另外一个是String类型,那么需要先把String类型的转换为Number,然后再执行==
  4. 如果其中一个是Boolean类型,另外一个是非Boolean类型,那么需要先把Boolean类型转换为Number类型,然后再执行==
  5. 如果其中一个是String或是Number类型,另外一个是Object类型,那么需要先把Object类型转换为primitive的类型,然后再执行==
  6. 其它情况返回false

在比较时,该运算符还遵守下列规则:

即使两个数都是NaN,等号仍然返回false,因为根据规则,NaN不等于NaN

undefined == null // true

false == 0 // true
true == 1 // true
true == '1' // true
true == 'foo' // false

'' == 0  //true
'  ' == 0 // true

'foo' == new String('foo') // true

[] == 0 // true

'abc' == new String('abc') // true

==带来的问题

从上面的分析,我们可以看到如果使用==,可能会带来一些问题:

  1. ==做判等时,如果两者类型不同,则需要做大量的类型转换,而这些类型转换规则并不是一眼就能看出来的,如果你稍不留意,结果就可能与你想像的大不相同
  2. 由于==可以允许任意的类型做判断,所以一些类型错误就有可能难以发现

比如:

true == 1 // true
true == 2 // false 与常识相反

true == '1' // true
true == '2' // false 与常识相反

0 == '' // true
'\r\n\t 111 \t' == 111 // true

true == 2false是因为true首先会被转化为1,然后再执行1 == 2,所以返回false。 大家可能会觉得最后一个例子'\r\n\t 111 \t' == 111很奇怪,为什么它会返回true?那是因为'\r\n\t 111 \t'在执行==前,必须先转换为Number,而这样会删除掉数值前后的空白符

什么时候可以使用==

我们经常给JavaScript初学者的其中一个建议就是尽量使用===而不是==,毫无疑问,这是完全正确的,特别是对一个初学者来说。但是有些情况下,我们可能会觉得使用==更合乎常理,但是他们其实不是看上去那样美的。

虽然你的代码只写了一次,但是它会被你和其他人读多次,所以代码的可读性是非常重要的。

undefinednull比较

如果你看完了上面的分析,那么你就会知道,使用==时,undefinednull之间是互等的,但是与其它值判等时都是false。所以,我们经常会用下在的代码来判断一个值是否为undefinednull

if (foo == null) {
  ...
}

乍一看,这样判断代码简短,但是可读性很弱,无法完全表达作者原有的意思。如果一个JavaScript初学者看到这段代码,他可能会以为你只是在检测null,但是如果是一个经验丰富的前端工程师的话,他可能会认为你写错,其实你是想用===。如下所示:

if (foo === undefined || foo === null) { 
  ... 
}

如果你还想更精简一点,那么可以这样写:

if (!foo) {
  ....
}

JavaScript中, false, null, 0, '', undefinedNaN都被当做false值。

比较字符串和数字

在处理与后端交互的Ajax请求返回结果时,我们经常需要去处理返回的以字符串形式展现的数值,如id: '111111'。这个时候,我们可能经常会这样去做比较:

if (id == 111111) {
  ....
}

这个时候,我们是去检查id111111或是'111111'。 但是,如果别人来看你的代码时,他会认为idNumber类型的。那么,为什么不直接告诉读你代码的人它是我们需要的是Number类型的呢?如果不是,我们需要先进行显示的类型转换。

if (Number(id) == 111111) {
  ....
}

比较对象和原生数据类型

由于==支持类型不同的值的比较的,所以你也用它来比较对象和原生数据类型值。

true == new Boolean(true)

如果你看到下面的这段代码,你会认为写这段代码的人是想做什么?

str == 'foo'
String(str) == 'foo'
str.valueOf() == 'foo'

如果你知道你比较的对象类型

比如,当我们以函数的形式调用String构造函数时,它会自动完成类型转换,如下代码所示:

if (String(foo) == 'foo') {
  ...
}

你知道String(foo)返回的肯定会是String类型,所以这里使用==是没有任何问题的,因为我们可以保证这里不可能会有任何的类型转换的。 但是,我们仍然可以不用这样做:

总结

虽然许多种情况下,看似使用==不伤大雅,但是不使用==已经是一条不成文的规则,不仅仅是对新手而已。

代码中少一些花哨,那么就更容易理解和维护。

参考

  1. ECMAScript 5: ToNumber
  2. ECMAScript 5: ToPrimitive
  3. The String Constructor Called as a Function
  4. JSPerf: Equality vs Strict Equality

Related Posts

Xin(Khalil) Zhang 14 February 2015
blog comments powered by Disqus