第四章 强制类型转换
1.值类型转换
JS中的强制类型转换总是返回标量基本类型值,如字符串、数字和布尔值,不会返回对象和函数。
2.抽象值操作
(1)ToString
负责处理非字符串到字符串的强制类型转化。例如null转化为“null”,undefined转化为“undefined”,true转为“true”。
对普通对象来说,除非自行定义,否则toString()返回内部属性[[class]]的值。如果对象有自己的toString()方法,字符串化时会调用该方法并使用其返回值。
将对象强制类型转换为string是通过ToPrimitive抽象操作完成的,后续详细介绍
数组的toString()方法经过了重新定义,将所有单元字符串化后再用逗号连接
var a = [1,2,3];
a.toString();//"1,2,3"
JSON字符串化
JSON.stringify(..)在将JSON对象序列化为字符串时也用到了ToString。JSON字符串化和toString()的效果基本相同,只不过序列化的结果总是字符串。
JSON.stringify(..)在对象中遇到undefined、function、symbol时会自动将其忽略,如果在数组中则返回null,以保证单元位置不变。
JSON.stringify(undefined);//undefined
JSON.stringify(function(){});//undefined
JSON.stringify([1,undefined,function(){}]);//"[1,null,null]"
JSON.stringify({a:2,b:function(){}});//"{"a":2}"
字符串、数字、布尔值和null的JSON.stringify(..)规则与ToString相同。
如果传递给JSON.stringify(..)的对象中定义了toJSON()的方法,那么该方法会在字符串化前调用,以便将对象转换为安全的JSON值。
(2)ToNumber
ToNumber对字符串的处理失败时返回NaN,但是对以0开头的十六进制数并不按十六进制处理,而是按十进制。
对象(包括数组)会首先被转换为相应的基本类型值,如果返回的是非数字的基本类型值,则再将其强制转换为数字。
为了将值转换为相应的基本类型值,抽象操作ToPrimitive会首先检查该值是否有valueOf()方法。如果有且返回基本类型值,就用该值进行强制类型转换,没有则用toString()的返回值来近些强制类型转换。如果valueOf()和toString()均不返回基本类型值,产生TypeError错误。
var a = {
valueOf:function(){
return "43";
}
};
var b = {
toString:function(){
return "42";
}
};
var c = [1,2];
c.toString = function(){
return this.join("");//join将数组中的元素放入字符串
};
Number(a);//43
Number(b);//42
Number(c);//12
Number("");//0
Number([]);//0
Number(["ab"]);// NaN
true 转换为1,false转换为0,undefined转换为NaN,null转换为0.
(3)ToBoolean
- 假值(falsy value)
js中的值可以被分为两类:可以被强制类型转换为false的值;其他
以下是假值:
undefined
null
false
“”
+0、-0、NaN
逻辑上说,假值列表以外的都叫真值。 - 假值对象(falsy object)
浏览器在某些特定情况下,在常规js语法基础上自己创建了一些外来值,就是“假值对象”。
假值对象看起来和普通对象并无二致(都有属性等),但将他们强制类型转换为布尔时返回false。比如document.all
var a = new Boolean(false);
var b = new Number(0);
var c = new String("");
var d = Boolean( a && b && c);
d;//true 说明a、b、c都为true
- 真值
真值就是假值列表之外的值。
[]、{}、function(){}都不在假值列表中,他们都是真值
3.显式强制类型转换
编码时应尽可能将类型转换表达清楚。
(1)字符串和数字之间的显示转换
是通过String(..)和Number(..)这两个内建函数实现的,注意前面没有new,不创建封装对象。
var a = 42;
var b = String(a);//"42"
var c = "3.14";
var d = Number(c);//3.14
String遵循ToString规则,Number遵循ToNumber规则。
除了String和Number,还有其他显示转换:
var a = 42;
var b = a.toString();//"42" 不能直接42.toString()????
var c = "3.14";
var d = +c;//3.14
因为toString对42这样的基本类型值不适应,所以js引擎会自动为42创建一个封装对象,再对该对象调用toString()。
一元加法运算符(+)
上例中一元运算符“+”显式地将c转换为数字。如果一元加法运算符后面本身跟着的就是一个数字型数据,那么得到的结果就是他本身,无论这个值是正数或负数,也不论这个值是整数、小数、科学计数表示。如果这个数字型数据是八进制或是十六进制最后会用十进制的数值表示.
+ 34 // 34
+ 3.4 // 3.4
+ 34e3 // 34000
+ 34e-3 // 0.034
+ -34 // -34
+ 012 // 八进制转换成十进制 10
+ 0x12 // 十六进制转换成十进制 18
如果后面跟的不是一个数字型数据类型,就会将其进行数据类型转换,比如如果后面是布尔值,将会得到如下结果
+ true // 1
+ false // 0
如果后面是字符串,这个转换成数字就类似于parseInt()或者parseFloat().最终得到的结果会是一个数字或者NaN。
一元运算符+的另一个常用用途是将日期对象转换为数字,返回结果为Unix时间戳,以毫秒为单位:
var d = new Date();
+d;//
也可用如下方法获得当前时间戳:
var timesramp = +new Date();
js有特殊的语法,构造函数没有参数时可以不带()。
可能会碰到var timestamp = +new Date;但这样代码可读性降低
将日期对象转换为时间戳并非只有强制类型转换这一种方法,还有如下:
var timestamp = new Date().getTime();
getTime 方法的返回值一个数值,表示从1970年1月1日0时0分0秒(UTC,即协调世界时)距离该日期对象所代表时间的毫秒数。
不过最好还是使用ES5的Date.now();
var timestamp = Date.now();
建议使用Date.now()获得当前时间戳,使用new Date().getTime()获得指定时间戳
一元减法运算符(-)
一元减法运算符的转换规则类似于上面的一元加法运算符,只是对于数字取其负结果(注意负负得正的规律)。
- 奇特的~运算符
字位操作符“非”
字位运算符只适用于32位整数,运算符会强制操作数使用32位格式。这是通过抽象操作ToInt32来实现的。ToInt32会先执行ToNumber强制类型转`换,再执行ToInt32.
~先将值强制类型转换为32位数字,再执行字位操作符“非”(对每个字位进行反转)。
~还有一种诠释:返回2的补码??
~x大致等同于-(x+1)
~42;// -(42+1) ==> -43
在-(x+1)中唯一能得到0的x值是-1.也就是说如果x为-1时,和一些数字值在一起时会返回假值0,其他情况返回真值。因为-1是一个哨位值,比如在js中indexOf方法找到子字符串就返回它的位置,否则返回-1。和indexOf一起将结果强制类型转换为真/假
var a = "Hello World!";
~a.indexOf("lo");// -4 真值
~a.indexOf("hh");//0 假值
if(~a.indexOf("hh")){...}
(2)显示解析数字字符串
解析字符串中的数字和将字符串强制类型转换为数字的返回结果都是数字。但两者之间有差别。
var a = "42";
var b = "42px";
Number(a);//42
parseInt(a);//42
Number(b);//NaN
parseInt(b);//42
解析允许字符串中有非数字字符,解析顺序从左至右,遇到非数字字符停止。转换不允许非数字字符,否则失败返回NaN。
ES5之前parseInt有一个坑,如果没有第二个参数来指定转换的基数,parseInt会根据字符串的第一个字符自行决定基数。。例如:
var hour = parseInt(selectedHour.value);
var minute = parseInt(selectedMinute.value);
console.log("Time you select is " + hour + ":" + minute);
该例子当小时为08、分钟为09结果是0:0因为8和0都不是有效的八进制数。将第二个参数设置为10可解决。
var hour = parseInt(selectedHour.value,10);
(3)显式转换为布尔值
与之前相同,Boolean(..)是显式地ToBoolean强制类型转换,但不常用。
与+类似,!显式地将强制类型转换为布尔值,它同时将真值转换为假值,所以显式地强制类型转换为布尔值的最常用的方法是!!,因为第二个!会将结果反转回原值:
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
!!a;//true
!!b;//true
!!c;//true
!!d;//false
!!e;//false
!!f;//false
!!g;//false
建议使用Boolean和!!进行显式强制类型转换。
4.隐式强制类型转换
即隐蔽的强制类型转换。
(1)隐式地简化
字符串和数字之间的隐式强制类型转换
var a = [1,2];
var b = [3,4];
a+b;// "1,23,4"
如果某个操作数是字符串或能够通过以下步骤转换为字符串,+将进行拼接操作。如果一个操作数是对象(包括数组),则首先对其调用ToPrimitive抽象操作,再调用[[DefaultValue]],以数字作为上下文。
简单来说,如果有一个操作数是字符串,则执行字符串拼接,否则执行数字加法。
a+""这样的隐式转换很常见,但和String有细微差别。a+""会对a调用valueOf方法,然后通过ToString抽象操作返回字符串,String(a)会直接调用ToString().
(2) || 和&&(操作数选择器)
|| 和&&返回的不一定是布尔值,而是两个操作数中的一个
对||来说如果第一个操作数为真,则返回第一个操作数,否则返回第二个操作数。
&&的第一个操作数为真则返回第二个操作数,反之返回第一个操作数。
a||b大致等于a?a:b
a&&b大致等于a?b:a
||是常见的“空值合并运算符”:a = a ||"hello"
&&是“守护运算符”:a&& foo()即只有在条件判断a通过时才会执行函数foo
(3)符号的强制类型转换
ES6允许从符号到字符串的显式强制类型转换,隐式强制类型转换会产生错误。
var s1 = Symbol("cool");
String(s1);//Symbol(cool)
var s2 = Symbol("not cool");
s2 +"";//TypeError
5.宽松相等和严格相等
==和===
==允许在比较中进行强制类型转换,===不允许
(1)抽象相等
注意:
- NaN不等于NaN
- +0不等于-0
字符串和数字之间的比较:将字符串Number化再比较
其他类型和布尔类型的比较:将布尔型Number化再比较
var a = "42";
var b = true;
a==b;//false
null和undefined之间的相等比较:
-
所以判断是否是null或undefined时可以直接if(a == null){...}
(6)抽象关系比较
- 比较双方先调用ToPrimitive,如果结果出现非字符串,就根据ToNumber规则将双方强制类型转换为数字来比较。
var a = [42];
var b = ["43"];
a<b;//true
a>b;//false
- 双方都是字符串,按字母顺序比较:
var a = ["42"];
var b = ["043"];
a<b;//false
看一个奇怪的例子:
var a = { b:42 };
var b = { b:43 };
a < b;//false
a ==b;//false
a > b;//false
a <= b;//true
a >= b;//true
根据规范,a <= b被处理为b<a,然后将结果反转,因为b<a返回false,所以a<=b为true。