原文:http://www.2ality.com/2011/12/strict-equality-exemptions.html
答案是:永远不要用.本文依次否定了五种看起来可以使用==的地方,同时解释了为什么.
JavaScript中有两个运算符用来判断两个值是否相等:
- 严格相等运算符===,必须类型相同且值相同.
- 普通的(宽容的)相等运算符==,在比较值是否严格相等之前,首先进行类型转换.
给JavaScript初学者的建议是:完全忘掉==,总是使用===.实践证明,这样做是完全正确的.有五种案例看起来可以反驳这一论点,但其实没有.从现在开始,我们的指导原则是:
比起简洁,我们更喜欢意图清晰的代码.
记住:你的代码只写一次,但可能要读很多次 – 请尽可能的让你的代码易读.
案例1:你知道你在比较什么
例如,如果使用的是typeof操作符,你可以确定它返回来的是个字符串.那么使用==就是安全的,因为我们可以确定这样的比较不会有任何的类型转换:
if (typeof x == "function") { ... }
可是,有两个理由让我们不去这样做:
- 一致性: 如果你在这里使用了==,你不会得到任何的好处,那还何必破坏统一的规则去使用它?
- 简单和性能: 通常来说, === 的操作更简单,因为它不用去转换它的操作数.在不同的JavaScript引擎上,该运算符的性能表现不同,但在大部分浏览器上,===总比==快,至少不会慢.
案例2: 比较undefined和null
使用==来比较的话,undefined和null是在一个相同的等价类上 – 他们和自己相等,同时互相之间也相等,但是不和其他任何值相等(包括那些JavaScript中的假值):
> null == null true > undefined == null true > false == null false > 0 == null false
因此,下面的if语句可以检测x是null或者是undefined(译者注:一箭双雕,jQuery1.81源码中有36处地方使用了== null).
if (x == null) { ... }
可是,代码的简洁性完全被代码缺少明确的意图这一缺点抵消掉了: 如果需要检测undefined值,那你还不如把它也写上. 否则,如果一个JavaScript新手读你的代码,他们会觉的你只是在检测null.如果一个老手读你的代码,他们也许会认为也许是你犯了一个错,这里应该用的是 ===.
if (x === undefined || x === null) { ... }
如果你的需求不是那么的严格,那么上面的代码可以简写为
if (!x) { ... }
只要x为下面列出的这些假值,条件检测就会通过.
undefined null false 0 ""
译者注:5种原始值类型中,刚好各有一个假值?不对,少了个NaN
案例3: 比较字符串和数字
案例场景是:你正在写和用户界面交互的代码或者处理服务器返回参数的代码.那么你很可能需要处理字符串类型的数字.如果x是这样一个字符串,你可以这样比较它:
if (x == 123) { ... }
但是为什么不告诉读你代码的人x不是一个数字呢,更好的写法是明确的将它转换成数字再进行比较.
if (Number(x) === 123) { ... }
案例4: 比较对象值和原始值
使用 ==,你可以比较两个原始值,也可以比较一个原始值和它的包装类型对象值:
> function isAbc(x) { return x == "abc" } > isAbc("abc") true > isAbc(new String("abc")) true
使用 === 的话,不行:
> new String("abc") === "abc" false
等号左边是一个对象值,等号右边是一个原始值.因此,他们没有相同的类型,也就不能严格相等.尽管如此,你仍然应该优先考虑,如何才能让读你代码的人清楚的知道你这行代码的意图.如果用下面这个判断的话
x == "abc"
你想要完成的任务是什么?
- 左侧的值真的既可能是一个字符串包装对象又可能是一个字符串原始值吗?这似乎不太可能,但如果真是这样的话,你应该在文档中很清楚的写明这到底是个什么样的操作.
- 你是想把对象值转换成原始值吗? 那么你可以写的更明确一点
String(x) === "abc"
- 你是想要拆箱出一个原始值吗?那么你应该这么写
x.valueOf() === "abc"
案例5: JavaScript是很灵活的 – 我写的代码也应该如此
这个论点是这样的:我希望我的代码能表现出和JavaScript同样的灵活性.并且 == 能帮我实现它.下面是体现JavaScript灵活性的例子,它能自动转换值的类型:
> "abc" + false 'abcfalse' > 3 + true 4 > +"73" 73
下面是上述论点的几个反驳:
- 标准的隐式转换并不可能总是符合你的期望.例如:
> !"false"
false
> 7 + "3"
'73'
> Number("")
0 - 宽容的相等判断和通常见到的隐式转换有着不同的表现:
> 2 == false
false
> 2 == true
false
> Boolean(2)
true - 明确的类型转化加上严格的相等判断能够产生更具描述性的代码.不好的写法:用宽容相等来实现灵活性
function is123Implicit(x) {
return x == 123;
}
> is123Implicit(123)
true
> is123Implicit(new Number(123))
true
> is123Implicit("123")
truefunction is123Explicit(x) {
x = Number(x);
return x === 123;
}
> is123Explicit(123)
true
> is123Explicit(new Number(123))
true
> is123Explicit("123")
true - 谁说你的代码必须要灵活?可以说JavaScript这种默认的灵活性是一个bug而不是特性.编写防御型的代码能更快的暴露出这些bug.一个更有防御性的函数is123Explicit()如下:
function is123Defensive(x) {
if (typeof x !== "number") {
throw new TypeError("Not a number: "+x);
}
return x === 123;
}
结论
我希望我已经说服了你,应该坚持以浅显易懂为原则,坚决不使用==来做判断.这样做是有道理的,不光针对新手,通常来说,代码中有更少技巧性的东西,就意味着代码有更好的可读性.