前言
我不得不承认类型转换是让人头痛的一个知识点,但这又是我们不得逃避的知识点,所以我们还是硬着头皮学吧,积少成多,总会发光发亮。
概念:将值从一种类型转换为另一种类型转换通常称为类型转换,这是显式的情况,隐式的情况称之为强制类型转换。
1.1 值类型转换
如何区分显式和隐式类型转换,显式转换很明显,而隐式转换通常是某些操作产生的副作用。
看下面代码:
var a=42;
var b=a+"";//隐式强制类型转换
var c=String(a);//显示强制类型转换
对变量b而言,强制类型转换是隐式的;由于+运算符的其中一个操作数是字符串,所以是字符串拼接操作,结果是数字42被强制类型转换为相应的字符串“42”。而String()则是将a显式强制类型转换为字符串。
1.2 抽象值操作
在继续深入介绍显式和隐式强制类型转换之前,我们需要先掌握字符串、数字和布尔值之间类型转换的基本规则。
1.2.1 ToString
它负责处理非字符串到字符串的强制类型转换。
基本类型值的字符串转换规则:
- null转换为"null"
- undefined转换为"undefined"
- true转换为"true"
- 数字遵循通用规则(注意:极小、极大的数字使用指数形式)
var a=1.05*1000*1000*1000;
a.toString();//1.05e9,指数形式
var a=[1,2,3];
a.toString();//"1,2,3"
JSON字符串化
工具函数JSON.stringify()在将JSON对象序列化为字符串时也用到了同toString。
注意: JSON字符串化并非严格意义上的强制类型转换,因为其中也涉及toString的相关规则,所以这里也顺带一下。
对于大多数简单值来说,JSON字符串化和toString()的效果基本相同。
JSON.stringify(42);//"42"
JSON.stringify("42");//""42""(含双引号的字符串)
JSON.stringify(null);//"null"
JSON.stringify(true);//"true"
所有安全的JSON值都可以使用JSON.stringify()字符串化。安全的JSON值是指能够呈现有效JSON格式的值。
什么是JSON非安全呢?如果在对象中遇到undefined、function、symbol时,JSON.stringify()会将其自动忽略,在数组中会返回null(以保证单元位置不变)。
例如:
JSON.stringify(undefined);//undefined
JSON.stringify(function(){});//undefined
JSON.stringify([1,undefined,function(){},4]);//"[1,null,null,4]"
JSON.stringify({a:2,b:function(){}});//"{"a":2}"
1.2.2 ToNumber
有时候我们需要将非数字值当作数值使用,比如数字运算。在ES5中定义了抽象操作ToNumber。
- true:1
- false:0
- undefined:NaN
- null:0
- “42”:42
- “42abc”:NaN
直接看图片的运行结果:(几乎涵盖我们所常见的数据类型)
好吧。上面那些可能你们都知道,那就搞些你们还不知道的。
并不是所有的对象都会返回NaN:
var a={
valueOf:function(){
return "42";
},
toString:function(){
return "43";
}
}
var b={
toString:function(){
return "44";
}
}
console.log(Number(a));
console.log(Number(b));
你看,上面竟然强制转换数字成功。
分析:
为了将值转换为相应的基本类型值,抽象操作会首先检查该值是否有valueOf()方法,如果有并且返回基本类型值,就使用该值进行强制类型转换;如果没有valueOf()就会检查是否含有toString(),如果有就是用返回值进行强制类型转换。
注意: 如果valueOf()和toString()均不返回基本数据类型值,会产生TypeError。
var b={
toString:function(){
return {c:4};
}
}
console.log(Number(b));
结果:
1.2.3 ToBoolean
先来看看哪些值是会被转换为false,称为假值:
- undefined
- null
- false
- +0、-0和NaN
- “”(非空字符串都为true)
上面这些值都会被强制转换为false:
console.log(Boolean(undefined));
console.log(Boolean(null));
console.log(Boolean(false));
console.log(Boolean(+0));
console.log(Boolean(-0));
console.log(Boolean(NaN));
console.log(Boolean(""));
我们可以理解为除了上述这些值强制类型转换为false之外,其它值都是true。
注意:所有对象都是真值。(非常重要)
就算利用上面的“假值”来生成的对象也是真值。
var a=new Boolean(false);
var b=new Boolean("");
var c=new Boolean(0);
console.log(Boolean(a),Boolean(b),Boolean(c));
利用new操作符返回的是一个对象,因此a、b、c都是对象,再对它进行强制类型转换都为true。
很多人会以为用假值生成的对象就是false,再看个例子:
var a=new Boolean(false);
if(!a){
console.log("能执行到这里?");
}else{
console.log("不能");
}
再次证实“对象”被强制类型转换是true.
那不是用new操作符生成的对象呢?
- []
- {}
- function(){}
都知道上面三种也是对象的子类型吧,它们也是对象。
console.log(Boolean([]),Boolean({}),Boolean(function(){}))
结论:对象皆为true。
1.2.4 宽松相等与严格相等
很多人将这样理解:“==”检查值是否相等,“ === ”检查值和类型是否相等。这样是不够准确的。
正确解释:“ == 允许在相等比较中进行强制类型转换,而 === 不允许强制类型转换。
1.2.4.1 字符串和数字之间的相等比较
var a=42;
var b="42";
a===b;//false
a==b;//true
因为没有强制类型转换,所以a=== b为false,42和”42“不相等。
而a == b是宽松相等,即如果两个值的类型不同,则对其中之一或两者都进行强制类型转换。
那到底是谁的数据类型转换了呢?
ES5定义规范:
(1)如果Type(x)是数字,Type(y)是字符串,则返回x==ToNumber(y)的结果。
(2)如果Type(x)是字符串,Type(x)是数字,则返回ToNumber(x)==y的结果。
那上面的举例就是字符串转化为数字类型。
1.2.4.2 其它类型和布尔类型之间的相等比较
== 最容易出错的一个地方是true和false与其它类型之间的相等比较。
var a="42";
var b=true;
a==b;//false
我们都知道"42"是一个真值,非空字符串的强制类型都为true,那么为什么a==b的结果不是true呢?这里很容易掉坑,需要引起我们的足够重视。
ES5定义规范:
(1)如果Type(x)是布尔类型,则返回ToNumber(x)==y的结果。
(2)如果Type(y)是布尔类型,则返回x == ToNumber(y)的结果。
分析:
var x=true;
var y="42";
x==y;//false
Type(x)是布尔值,所以ToNumber(x)将true转换为1,变成1== ”42“,二者的类型仍然不同,”42“根据规则类型转换为42,最后变成1==42,结果为false。进行两次强制类型转换
反过来也这样分析:
var x="42";
var y=false;
x==y;//false
Type(y)是布尔值,所以ToNumber(y)将false强制类型转换成0,然后"42"==0再转换成42 == 0,结果为false。
根据上述定义的规则,只要与true或false进行==比较,是对boolean类型值进行ToNumber()操作。而非将"42"进行ToBoolean()操作。谨记,勿掉坑。
如果我们对上述这个比较嫌麻烦或不理解,那我们的代码应该这样写:
var a="42";
if(a==true){//不要这样使用,条件判断不成立
}
if(a===true){//也不要这样用,条件判断不成立
}
if(a){//这样显式用法没问题
}
if(!!a){//这样的显式用法更好
}
if(Boolean(a)){//也很好
}
后面三个避免了== true和== false之后我们就不必担心这些坑了。
1.2.4.3 null和undefined之间的相等比较
null和undefined之间的== 也涉及隐式强制类型转换。
ES5的定义规则:
(1)如果x为null,y为undefined,则结果为true。
(2)如果x为undefined,y为null,则结果为true。
由定义可知,在 == 中null和undefined相等,除此之外与其他值都不存在这种情况。即==其他值都为false。
看下面示例:
var a=null;
var b;
a==b;//true
a==null;//true
b==null;//true
a==false;//false
b==false;//false
a=="";//false
b=="";//false
a==0;//false
b==0;//false
看下面代码:
var a=doSomething();
if(a==null){
//...
}
条件判断为a==null仅在doSomething()返回null或undefined时才成立,除此之外其它值都不成立,包括("",0,false)这样的假值。
1.2.4.4 对象和非对象之间的相等比较
关于对象(对象、函数、数组)和标量基本类型(字符串、数字、布尔值)之间的相等比较。
ES5定义的规范:
(1)如果Type(x)是字符串或数字,Type(y)是对象,则返回x==ToPrimitive(y)的结果。
(2)如果Type(x)是对象,Type(y)是字符串或数字,则返回ToPromitive(x) == y的结果。
这里没有提到布尔值,原因是前面我们介绍了其规则规定了布尔值会先被强制类型转换为数字。
看下面代码:
var a=42;
var b=[42];
a==b;//true
[42]首先会调用ToPromitive抽象操作,返回”42“,变成"42"==42,然后变成42 == 42,最后二者相等。
var a="abc";
var b=Object(a);//相当new String(a)
if(a==b){
console.log("true")
}
a==b;//true
a===b;//false
因为b通过ToPromitive进行强制类型转换,并返回标量基本数据类型"abc",与a相等。