13 April 2013

(原创翻译)JS中的判断为真与相等(Truth, Equalit and JavaScript)

以前也碰到过几次 if(x) 的时候出现预想外的错误,也翻看过许多相关资料,但是一直印象不深。

今天又碰到 if({}) 为true的问题,找到一篇深入介绍JS中判断相等判断true原理的文章,正好闲来无事,就动手翻译一下吧。一是加深印象,而是练练英语。翻译的不好,大家将就看哈,实在觉得我的表达看不懂的可以参考原文!

原文链接


### 译文开始 ———-

哪怕你已经不是一个JS菜鸟了,你可能还是容易被下面的代码所迷惑

if ([0]) {
    console.log([0] == true); //false
    console.log(!![0]); //true
}

或者

if ("potato") {
    console.log("potato" == false); //false
    console.log("potato" == true); //false
}

好消息是,有一个所有浏览器都遵循的判断标准。一些人可能会告诉你要害怕强制类型转换,并且应该尽量避免使用它们。但是我希望说服你,强制类型转换是一种杠杆(或者理解为双刃剑?),你要做到的不是避免使用它们,你至少应该去理解它们。

x是true吗? x==y吗?在JS中判断为真或者判断相等的问题,主要出现在这三种情况下:

  1. 条件语句和操作符(if,三元运算符,&&,   等)
  2. 相等判断符 ==
  3. 严格相等判断符 ===

下面就让我们看看在这三个方面中各自会发生什么吧…

条件语句

在JS中,所有条件语句和操作符都遵循相同的规则。我们将用IF语句来做范例。

在IF语句中,它将利用一个抽象方法ToBoolean,强制将判断条件的结果转换为一个boolean类型的结果。由ES5 定义的ToBoolean方法的算法为:

Argument Type Result
Undefined false
Null false
Boolean The result equals the input argument (no conversion).
Number The result is false if the argument is +0−0, or NaN;
otherwise the result is true.
String The result is false if the argument is the empty String (its length is zero);
otherwise the result is true.
Object true.

也就是说,在JS中,判断为true的有(true,”potato”,36,[1,2,4] and {a:16})。

判断为false的有(false,0,”“,null and undefined)。

现在我们知道了,在我们最开始的例子中,if([0]),一个数组是一个对象,一个对象会被强制转为true。

下面是一些补充例子,对于一些结果可能会让你惊讶,不过它们都是遵循上面说到的规则的:

var trutheyTester = function(expr) {
    return expr ? "truthey" : "falsey"; 
}
 
trutheyTester({}); //truthey (an object is always true)
 
trutheyTester(false); //falsey
trutheyTester(new Boolean(false)); //truthey (an object!)
 
trutheyTester(""); //falsey
trutheyTester(new String("")); //truthey (an object!)
 
trutheyTester(NaN); //falsey
trutheyTester(new Number(NaN)); //truthey (an object!)

判断相等操作符(==)

== 用来判断相等比较宽容。

两个值会被判断为相等,哪怕它们不是相同的类型,因为操作符会在比较前强制将其中一个或者两个值转换为相同类型(通常是number类型)。

许多开发者认为这是一件相当可怕的事情,至少一位出名的JSer毫无疑问的推荐彻底的避免使用 == 操作符。

这种回避的策略让我很困扰,因为你在没有完全由内自外的理解一门语言之前,你都没法完全的掌控它,而恐惧与逃避是知识的天敌。

另外,假装 == 不存在并不能解决问题,因为强制类型转换在JS中比比皆是!

它在条件语句中有出现(前面展示过的),它在数组的索引中有出现,还有许多。

而且,当强制转换被安全的运用,它能让你的代码看起来更简洁,优雅,更有可读性。

好了,总而言之,下面我们来看看ECMA是怎么定义 == 工作的。

它真的没有那么的恐怖,只要记住的是 undefinedNull 能相互相等(并与其他任何都不相等),还有大部分类型都被强制转换为number类型以加速判断了:

Type(x) Type(y) Result
x and y are the same type See Strict Equality (===) Algorithm
null Undefined true
Undefined null true
Number String x == toNumber(y)
String Number toNumber(x) == y
Boolean (any) toNumber(x) == y
(any) Boolean x == toNumber(y)
String or Number Object x == toPrimitive(y)
Object String or Number toPrimitive(x) == y
otherwise… false

如果算法的结果是一个表达式,它将被重新申请计算,直到结果是一个boolean值。

toNumbertoPrimitive 是内置方法,它将会把输入值按照下面规则转换:

ToNumber
Argument Type Result
Undefined NaN
Null +0
Boolean The result is 1 if the argument is true.
The result is +0 if the argument is false.
Number The result equals the input argument (no conversion).
String In effect evaluates Number(string)
“abc” -> NaN
“123″ -> 123
Object Apply the following steps:

 

1. Let primValue be ToPrimitive(input argument, hint Number).
2. Return ToNumber(primValue).

ToPrimitive
Argument Type Result
Object (in the case of equality operator coercion) if valueOf returns a primitive, return it. Otherwise if toString returns a primitive return it. Otherwise throw an error
otherwise… The result equals the input argument (no conversion).

下面是一些例子,我将会用模拟算法来一步步展示强制转换的算法是怎么工作的:

[0] == true;

//EQUALITY CHECK...
[0] == true; 
 
//HOW IT WORKS...
//convert boolean using toNumber
[0] == 1;
//convert object using toPrimitive
//[0].valueOf() is not a primitive so use...
//[0].toString() -> "0"
"0" == 1; 
//convert string using toNumber
0 == 1; //false!
“potato” == true;

//EQUALITY CHECK...
"potato" == true; 
 
//HOW IT WORKS...
//convert boolean using toNumber
"potato" == 1;
//convert string using toNumber
NaN == 1; //false!
“potato” == false;

//EQUALITY CHECK...
"potato" == false; 
 
//HOW IT WORKS...
//convert boolean using toNumber
"potato" == 0;
//convert string using toNumber
NaN == 0; //false!
object with valueOf

//EQUALITY CHECK...
crazyNumeric = new Number(1); 
crazyNumeric.toString = function() {return "2"}; 
crazyNumeric == 1;
 
//HOW IT WORKS...
//convert object using toPrimitive
//valueOf returns a primitive so use it
1 == 1; //true!
object with toString

//EQUALITY CHECK...
var crazyObj  = {
    toString: function() {return "2"}
}
crazyObj == 1; 
 
//HOW IT WORKS...
//convert object using toPrimitive
//valueOf returns an object so use toString
"2" == 1;
//convert string using toNumber
2 == 1; //false!

严格模式相等操作符===

这个就简单多了。当两值类型不同时,总是得到false。如果它们是相同类型,那么凭直觉测试得到相等的情况:对象必须指向相同的对象,字符串必须包含相同的字符,其他原始类型必须拥有相同的值。

NaN,null和undefined永远不会===其他类型,而NaN甚至===自己都为false。

Type(x) Values Result
Type(x) different from Type(y) false
Undefined or Null true
Number x same value as y (but not NaN) true
String x and y are identical characters true
Boolean x and y are both true or both false true
Object x and y reference same object true
otherwise… false

一些过分使用严格匹配的常见的例子:

(2015.5.10补充:使用严格相等可以略微加快运算效率,在确定类型的情况下推荐使用===)

//unnecessary
if (typeof myVar === "function");
 
//better
if (typeof myVar == "function");
因为typeof返回的是一个字符串,这个操作将会一直是比较两个字符串,所以==是100%coercion-proof(不知道怎么翻,大概是100%严格匹配?)

//unnecessary
var missing =  (myVar === undefined ||  myVar === null);
 
//better
var missing = (myVar == null);
null和undefined是相互==的

提示:因为有(非常小)的出错可能,undefined变量被重新定义了,所以与null比较会相对安全些。

//unnecessary
if (myArray.length === 3) {//..}
 
//better
if (myArray.length == 3) {//..}

Posted in 2013-04-13 20:33