强制类型转换
强制类型转换是工作中经常用到的但又常常忽视的,碰到它的时候我的内心带着一丝丝的不屑:这么简单的东西还值得我花时间去研究吗?但素,当我遇到了
if([]) { console.log(1) } // 1
[] == false; // true
一脸懵逼的我在风中凌乱。于是乎,只得翻开书本诚心拜读。
强制类型转换返回的总是基本类型值,像数字,字符串,布尔值之类的,不会返回对象和函数。
抽象值操作
1. ToString
基本类型值的字符串化规则:null转换为"null",undefined转换为"undefined",true转换为"true"。数字转换为字符串的时候要注意极大和极小的数会使用指数形式:
var a = 1.07 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
a.toString() // "1.07e21"
极大和极小的数会表示成指数形式是我们经常忽略的一个点,导致在很多地方产生了疑惑,比如
parseInt(0.000000008); // 8
实际上就是先把0.000000008转换为了字符串"8e-7".
数组的默认toString()方法经过了重新定义,将所有的单元字符串化以后再用","连接:
var a = [1,2,3];
a.toString(); // "1,2,3"
如果两个数组相加:
var a = [1,2,3];
var b = [4,5];
a + b; // "1,2,34,5";
就是因为a b先转换为了字符串,然后 + 就进行了拼接操作。
JSON字符串化
所有安全的JSON值都可以使用JSON.stringify()字符串化。那么,什么是不安全的JSON值呢?undefined、function、symbol和包含循环引用的对象都不符合JSON结构标准。
我们有时候使用JSON.parse(JSON.stringify(..))来进行深度拷贝,但当对象中含有undefined、function和symbol时,使用JSON.stringify(..)序列化后就会丢失,在数组中包含则会返回null(以保证单元位置不变):
JSON.parse(JSON.stringify([1, undefined, function(){}, 3])); // [1, null, null, 3]
JSON.parse(JSON.stringify({a: 2, b: function(){} })); // {a: 2}
2. ToNumber
ToNumber的作用就是把非数字值当作数字来使用,其中true转换为1,false转换为0,undefined转换为NaN, null转换为0.
对象(包括数组)会首先被转换为相应的基本类型值(先检查该值是否有valueOf()方法,如果有并且返回基本类型值,就使用该值进行强制类型转换,如果没有就使用toString()的返回值进行转换),如果返回的是非数字的基本类型值再将其强制转换为数字。
我们分析一下这个例子:
Number([]); // 0
首先[]调用valueOf()方法,返回值是[],不是基本类型然后调用toString(),返回值是""; 然后Number("") === 0
3. ToBoolean
列举一下假值列表:
- undefined
- null
- false
- +0、 -0、NaN
- ""
列出了假值列表,那像[]、{}这些也就是真值了
显示强制类型转换
1. 字符串和数字之间的显式转换
- 日期转换为数字
一元运算+可以将日期对象强制转换为数字,返回结果为时间戳:
+new Date()
还有其他方法获取时间戳,比如:
new Date().getTime();
Date.now();
- ~运算符(字位操作"非")
这个运算符有这么一个用处,~x
大致等同于-(x+1),在-(x+1)中唯一能够得到0的x值是-1。就是说如果x为-1,~和一些值在一起会返回假值0.
比如indexOf()查找字符串的位置,如果找的到返回子字符串的位置,如果找不到返回-1.但是如果我们直接写"hello".indexOf("e") === -1 不太好,这样在代码中暴露了底层实现的细节,这时候~运算符就可以登场了。
var a = "hello";
~a.indexOf('lo'); // -4 真值
~a.indexOf("ol"); // 0 假值
2. 显示转换为布尔值
Boolean()是显示的ToBoolean强制类型转换。
var a = "0";
var b = [];
var c = {};
var d = "";
var e = 0;
var f = null;
var g;
Boolean(a); // true;
Boolean(b); // true;
Boolean(c); // true;
Boolean(d); // false;
Boolean(e); // false;
Boolean(f); // false;
Boolean(g); // false;
一元运算符!显示地将值强制类型转换为布尔值,同时将真值反转为假值(或者将假值反转为真值)。所以显示强制类型转换为布尔值最常用的方法是!!
if()语句中如果没有显示强制类型转换,会自动隐式地进行ToBoolean转换。在看开头那个if语句,[]进行布尔转换是真值,所以可以进到if语句里面。
|| 和 &&
在JavaScript中他们返回的并不是布尔值,而是两个操作数中的一个。例如:
var a = 1;
var b = 'a';
var c = null;
a || b // 1
a && b // 'a'
c || b //'a'
c && b // null
||
和&&
首先会对第一个操作数执行条件判断,如果不是布尔值,就先进行ToBoolean强制类型转换,然后再执行条件判断
对于||
, 如果条件判断结果为true就返回第一个操作数的值,如果为false就返回第二个操作数的值
对于&&
, 如果条件判断结果为true就返回第二个操作数的值,如果为false就返回第一个操作数的值
既然返回的不是true或false,为什么表达式在if语句中没有问题呢?因为这些条件判断表达式最后还会执行布尔值的隐式强制类型转换啊
如果要避免隐式强制类型转换,可以这样:
if (!!c || !!b) {}
抽象相等
1. 字符串和数字之间的相等比较
ES5规范定义:
- 如果Type(x)是数字,Type(y)是字符串,则返回x == ToNumber(y)的结果
- 如果Type(x)是字符串,Type(y)是数字,则返回ToNumber(x) == y的结果
2. 其他类型和布尔类型之间的相等比较
规范规定:
- 如果Type(x)是布尔类型,则返回 ToNumber(x) == y 的结果
- 如果Type(y)是布尔类型,则返回 x ==ToNumber(y) 的结果
所以再看开头那个例子:
[] == false; // true
按照规范,false 先被转换为数字类型0,就变成了 [] == 0; 然后[]
被转换为基本类型,因为valueOf方法返回的不是基本类型,所以调用toString()方法返回""
, 也就是 "" == 0
; 然后就是字符串和数字之间的比较,""
被转换为数字类型0,自然0 == 0
成立,返回true。
3. 对象和非对象之间的相等比较
ES5规范规定:
- 如果Type(x) 是字符串或数字,Type(y) 是对象,则返回 x == ToPrimitive(y) 的结果
- 如果Type(x) 是对象,Type(y) 是字符串或数字,则返回 ToPrimitive(x) == y 的结果
ToPrimitive将对象转换为基本类型值,首先调用valueOf()方法,如果没有该方法或者返回值不是基本类型,就调用toString()方法;如果均不返回基本类型值,就会产生TypeError错误。
例如:
var a = 42;
var b = [42];
a == b; // true
[42]先调用ToPrimitive抽象操作,返回"42",变成"42" == 42,然后“42”转成数字类型:42 == 42,返回true。
最后再看一个例子:
[] == ![] // true
根据ToBoolean规则,先进行布尔值的显式强制类型转换,所以[] == ![]
变成了[] == false
,然后前面说过[] == false
成立,所以上面表达式也成立啦