深入理解Javascript: 比较

开始这个之前我们先看几个例子

var a = [1, 2]
var b = [1, 2]
a == [1, 2] // false
a == b // false

var c = {a: 1, b: 2}
var d = {a: 1, b: 2}
c == d // false

var str = 'test'
str == 'test' // true

str.toUpperCase() // 'TEST'
str // 'test'

为什么有这种现象发生呢?

1.JS的原始值与对象

原来JS分为两种对象,一个是原始类型,有undefined、null、布尔值、数字和字符串;一个是对象类型(数组、函数等);

原始类型值是不可更改的,任何方法都无法更改一个原始值,如上面例子中str经过转换大写之后还是‘test’;原始类型值的比较是值的比较;

对象类型值的比较并非值的比较,即使两个对象包含同样的属性及相同的值,如上例子的a与b、c与d的比较都为false;

对象的比较为引用的比较,当且仅当它们引用同一个基对象时,它们才相等,如下:

var a = [1, 2]
var b = a
a == b // true

2.=====

再来看一个例子:

1 == '1' // true
1 === '1' // false
var undefined;
undefined == null; // true
1 == true;   // true
2 == true;   // false
0 == false;  // true
0 == '';     // true
NaN == NaN;  // false
[] == false; // true
[] == ![];   // true

为啥会有前面两行的结果呢?

原来‘==’运算符用来检测两个操作数是否相等,这里相等的定义非常宽松,可以允许进行类型转换;

而‘===’运算符用来检测两个操作数是否严格相等,会比较两个操作数的类型是否相同;

===运算规则

‘===’运算符首先计算其操作数的值,然后比较这两个值,比较过程没有任何类型转换;

  • 如果两个值类型不同,返回false;

  • 如果两个值都为null或者undefined,返回true;

  • 如果两个值都为布尔值true或者false,返回true;

  • 如果其中一个为NaN,则返回false,即使另一个也为NaN;

  • 如果两个值为数字且数值相等,则返回true,0与-0也返回true;

  • 如果两个字符串的长度或者内容不同,则返回false;

  • 如果两个引用值指向同一个对象,则返回true,其他对象比较都返回false;

==运算规则

‘==’运算符跟‘===’类似,不过比较并不严格。如果两个操作数类型相同,则和上文所述的严格相等的比较规则一样;

如果两个操作数不是同一类型,那么相等运算符会尝试进行一些类型转换,然后进行比较:

  • 如果一个值为null,另一个为undefined,则它们相等;

  • 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值进行比较;

  • 如果其中一个是true,则将其转换为1再进行比较;如果是false,则转换为0再进行比较;

  • 如果一个值为对象,另一个值是数字或字符串,则使用下一小节要讲的类型转换规则转换为原始值,然后再进行比较;对象通过toString()方法或valueOf方法转换为原始值;JS语言核心的内置类首先尝试使用valueOf(),再尝试toString(),除了日期类,日期类只使用toString()转换。那些不是内置对象则通过各自实现中定义的方法转换为原始值;

  • 其他不同类型之间的比较均不相等;

'1' == true返回true,其中true转换为1,‘1’转换为1,因为两个数字的值相等,所以返回true。

3.类型转换

类型转换有隐式和显式转换,下图为转换的规则:

http://7xj29n.com1.z0.glb.clouddn.com/类型转换.jpg

显示转换

做显示转换最简单的方法就是使用Boolean()、Number()、String()或Object()函数;

注意这些函数与它们作为函数构造器是有区别的,作为函数构造器是使用new Number(),这种得出来的是属于包装对象;

如下:

Number("3")
new Number("3")
typeof Number("3") // number
typeof new Number("3") // object

隐式转换

隐式转换是通过上下语境或者运算符来转换的;

例如‘+’运算符有一个操作数为字符串时,另一个操作数不为字符串时会隐式转换为字符串;’-‘运算符会隐式把两个操作数转换为数字;

再如一元运算符‘!’会把操作数转换为布尔值并取反;如:

12 + '1' // '121'
new Number("3") - [1] // 2
!![] // true

对应的转换规则如上面图中所示,下面只来说说对象转换为原始值的方式(这里只谈原生对象,而非宿主对象的转换,宿主对象根据各自的算法进行转换);

1.对象转换为字符串

  • 如果对象具有toString()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 否则JS会调用valueOf()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 如果对象无法从上面两个方法获得一个原始值,这时它将抛出一个类型错误异常;

例如计算[1, 2, 3] + 'abc'返回'1,2,3abc'

首先[1, 2, 3]调用toString()方法返回‘1,2,3’然后再执行字符串的+运算,返回'1,2,3abc'

2.对象转换为数字

这个与上面转换为字符串相反,先调用valueOf(),失败再尝试toString();

  • 否则JS会调用valueOf()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 如果对象具有toString()方法,且调用这个方法返回一个原始值,则对象先转换为原始值,然后再通过上图的转换规则转换为字符串;
  • 如果对象无法从上面两个方法获得一个原始值,这时它将抛出一个类型错误异常;

例如计算[1, 2, 3] - 1返回NaN

首先[1, 2, 3]调用valueOf()方法,而此方法返回一个对象而不是原始值,则调用toString()方法,返回‘1,2,3’然后再转换为数字,根据字符串转换为数字的规则,为NaN,NaN - 1返回NaN

注意:日期对象有点特别,通过valueOf和toString方法返回的原始值将被直接使用;

4.后记

通过上面的一些例子我们知道JS原始类型与对象类型的一些转换规则以及JS的内部实现,有机会再聊聊JS的常见运算符。