开始这个之前我们先看几个例子
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.类型转换
类型转换有隐式和显式转换,下图为转换的规则:
显示转换
做显示转换最简单的方法就是使用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的常见运算符。