判断大揭秘

本篇文章选自作者angus-c的博客,博客地址

由我这个英语垃圾进行翻译………………


JavaScript中的真或假

就算你不是一个JS新手,也会被下面的语句感到迷惑吧:

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

又或者像这样:

1
2
3
4
if("potota"){
console.log("potato" == false); //false
console.log("potato" == true); //false
}

好消息是所有的浏览器有一个统一的标准。一些作者将会告诉你如何使用代码去解决这个让人害怕的矛盾。我希望你要相信这个矛盾会让你在学习中更上一个台阶(或者彻底的理解它),你要做的是尝试理解而不是逃避。

x是’true’吗?x和y相等吗?有关于是否为真,是否相等的问题是JS三个主要领域中的核心问题。条件的声明和操作(比如if语句,三元判断法,与或非等等),等式的操作(==),和严格等式的操作(===)。让我们看看这些都会发生些那些有趣的事情呢?

条件语句

在JS中,所有的条件声明和操作都遵循着相同的强制规范,我们使用if声明举一个例子:
当我们声明了一个if表达式的时候,他会将返回的结果强制的变为布尔值。这是使用了一种抽象的ToBoolean模块,一个在ES5中所定义的一种算法

数据类型 返回结果
Undefined false
Null false
Boolean 结果等同于你输入的值
Number +0,-0,和NaN的结果为false,其他数字为true
String 如果字符串为空(长度为0)返回的结果为false,其他情况为true
Object true

JS有一些经典的真值和假值:
真值(true,'potato',36,[1,2,3],{a:16}),假值(false,0,' ',null,undefined)

现在我们就可以弄清关于开场时我们举得几个例子,if([0])能够运行的原因是:一个数组就是一个对象,所有的对象返回的结果都为true。为什么会这样呢?就是因为if表达式将[0]解析成为一个对象,所以结果就成为了true,这样就会运行if表达式后面的代码。

这里有一些小例子,有些结果或许会出乎你的意料,但是他们始终坚持上述规定的简单规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var trutheyTester = function(expr) {
return expr ? "truthey" : "falsey";
}
trutheyTester({}); //truthey (对象永远输出true)
trutheyTester(false); //falsey
trutheyTester(new Boolean(false)); //truthey (一个对象!)
trutheyTester(""); //falsey
trutheyTester(new String("")); //truthey (一个对象!)
trutheyTester(NaN); //falsey
trutheyTester(new Number(NaN)); //truthey (一个对象!)

等式算法(==)

==的判断并不会那么严格,两个不同类型的值也有可能相等。在进行两个不同类型的值进行判断相等运作时,这种算法会将==式的一边或两边暴力的转化为单一的数据类型(通常是数字)。毫无疑问,最后一个众所众知的JS权威专家建议尽量避免使用==运算符。

这种回避策略使我不安的一点是因为你没有完全的明白这个语言的全部的时候,你是无法掌握这个语言的,而且一味的回避和害怕是知识的敌人。除此以外,当==运算符出现并且你无奈的想要理解它的时候,假装==运算符不存在也不会让你摆脱这种疑惑,因为这种暴力算法存在于JS的每一个角落。他存在于条件表达式中(向我们刚刚看到的那种),在数组的索引中,存在于越来越多的地方。所以使用安全模式这种强制性的要求很有必要,这会让你的更加简洁,优雅并且具有可读性。

无论如何,无奈的怒吼一下之后,我们还是要了解一下在EMEC的定义中==是怎么工作的。这并不是那么的恐怖。只要记住undefinednull两者是相等的,并且大多数其他的数据类型为了便利的进行比较将会暴力的转化为一个数字。

数据类型x 数据类型y 比较结果
x和y是相同的类型 参考严格模式下的算法(===)
Null Undefined true
Number String x == toNumber(y)
String Number toNumber(x) == y
Boolean (any) toNumber(x) == y
(any) Boolean x == toPrimitive(y)
Object String or Number toPrimitive(x) == y
其他情况 false

下面我们参考一下上面的描述进行几个小例子:

1
2
3
console.log('1'==1) //true
console.log('s'== NaN) //false
console.log( null == undefined) //true

当结果是一个表示式的时候,算法将会再一次的运行,直到结果成为一个布尔值。toNumbertoPrimitive是当进行强制性的转化数据类型时隐藏在==运算符内部的模块,他们的转化规则如下:

ToNumber

数据类型 返回结果
Undefined NaN
NaN +0
Boolean 如果是true时,值为1,如果是false时,值为+0
Number 结果等于输入的值
String 取决于字符串的内容是否为数字,’abc’->NaN ‘123’->123
Object 运行步骤:1使用ToPrimitive将对象转化为原始类型的值 2再将原始值使用ToNumber转化为数字

ToPrimitive

数据类型 返回结果
Object (在强制的等式运算符情况下)如果valueOf返回值是一个原始值,返回它,其他情况如果toString返回值是一个原始值得,返回它。再或者的情况下会返回一个error
其他情况 值就是输入的值

这里有一些例子,我将用伪代码来解释这些强制算法是怎么一步一步的运算的:

[0] == true;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//检查是否相等...
[0] == true;
//如何工作
// 使用toNumber转化上面的布尔值
[0] == 1;
//使用toPrimitive转化对象
//[0].valueOf() 不是一个原始值,所以使用
//[0].toString() -> '0'
'0' == 1;
//使用toNumber转化字符串
0==1;// false

‘potato’ == true

1
2
3
4
5
6
7
8
9
10
//检查是否相等...
'potato' == true;
//如何工作
// 使用toNumber转化上面的布尔值
'potato' == 1;
//使用toNumber转化字符串
NaN == 1;// false

‘potato’ == false

1
2
3
4
5
6
7
8
9
10
//检查是否相等...
'potato' == false;
//如何工作
// 使用toNumber转化上面的布尔值
'potato' == 0;
//使用toNumber转化字符串
NaN == 0;// false

object with valueOf

1
2
3
4
5
6
7
8
9
//检查是否相等...
crazyNumeric = new Number(1);
crazyNumeric.toString = function(){return '2'};
crazyNumeric == 1
//如何工作
//使用toPrimitive转化对象
//valueOf() 返回一个原始值
1 == 1;//true

object with toString

1
2
3
4
5
6
7
8
9
10
11
12
13
//检查是否相等...
var crazyObj = {
toString: function() { return '2'}
}
crazyNumeric == 1
//如何工作
//使用toPrimitive转化对象
//valueOf() 返回一个对象 所以我们使用 toString
"2" == 1;
//将字符串转化为数字
2 == 1 //false!

严格相等模式(===)

这是很简单的一个模式了。如果运算符两边的数据类型不同,答案永远都是’false’。如果他们是相同的数据类型,我们直观的都可以判断到:两个值的引用必须来自同一个对象,数组,或者函数。字符串必须包含相同的字符格式,各个位上的字符都相等。其他原始值也必须是相等的值和长度。NaN,nullundefined永远不等于其他数据类型,NaN甚至不等于它自己。

数据类型x 比较结果
x和y是不相同的类型 false
Null or Undefined true
Number x和y相等但不是NaN true
String x和y字符格式相等,长度相等 true
Boolean x和y同时为true或false true
Object x和y引用自同一个对象 true
其他情况 false

常见的不宜使用严格相等的例子

1
2
3
4
5
//多余的
if(typeof myVar === 'function');
//推荐
if(typeof myVar == 'function');

因为typeOf返回的是字符串,等式两边永远是两个字符串在进行比较。所以这个==就等同于===。

1
2
3
4
5
//多余的
var missing = (myVar === undefined || myVar === null);
//推荐
var missing = (myVar == null);

null 和 undefined 在 == 中是相等的。

注意:因为将变量定义为undefined会有一点点小风险,所以变量等于null是比较安全的做法。

1
2
3
4
5
//多余的
if (myArray.length === 3)
//推荐
if (myArray.length == 3)

这个就不用说太多。科科…

引用文章:
ECMA-262 第五版
11.9.3 The Abstract Equality Comparison Algorithm
11.9.6 The Strict Equality Comparison Algorithm
9.1 toPrimitive
9.2 toBoolean
9.3 toNumber