强制类型转换

今天来谈谈JavaScript中的强化类型转换。
将值从一种类型转换为另一种类型通常称为类型转换,这是显式情况,隐式的情况称为强制类型转换。这里再将强制类型转换区分为显式强制类型转换隐式强制类型转换。当然这个区分是相对而言的。

抽象值操作

ToString

ToString负责处理非字符串到字符串的强制类型转换。转换规则:null转换为"null"undefined转换为"undefined"true转换为"true"、数字的转换遵守通用规则,极大和极小值使用指数形式:

1
2
var a = 2.34 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000 * 1000;
a.toString(); // "2.34e21"

对普通对象来说,如果有自己的toString()方法,字符串化时会调用该方法并使用其返回值,否则Object.prototype.toString()返回内部属性[[Class]]的值,如"[Object Object]"
对数组来说,数组的默认toString()方法经过的重新定义,将所有的单元字符串化后再用,连接起来:

1
2
var a = [1,2,3];
a.toString(); // "1,2,3"

ToNumber

ToNumber负责将数字值当作数字使用。转换规则:true转换为1false转换为0undefined转换为NaNnull转换为0
ToNumber对字符串的处理基本遵守数字常量的规则,处理失败时返回NaN,不同之处是ToNumber对以0开头的十六进制数并不按十六进制处理,而是按十进制。

对象(包括数组)会首先转换成相应的基本类型值,如果返回的是非数字类型的基本类型值,则再遵循以上规则将其强制转换为数字。这里依赖于ToPrimitive操作,先检查该值是否有valueOf()方法,如果有则返回基本类型值,再使用该值进行强制类型转换;如果没有就使用toString()的返回值来进行强制类型转换。
如果valueOf()toString()均不返回基本类型,会产生TypeError错误。
注:使用Object.create(null)创建的对象[[Prototype]]属性为null,并且没有valueOf()toString()方法,因此无法进行强制类型转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var a = {
valueOf: function(){
return "42";
}
};
var b = {
toString: function(){
return "42";
}
}
var c = [4,2];
c.toString = function(){
return this.join("");
}

Number(a); // 42
Number(b); // 42
Number(c); // 42
Number(""); // 0
Number([]); // 0
Number([1,2,3]); // NaN

ToBoolean

首先明确数字1和0跟布尔值true和false并不是一回事。JavaScript规定了一些可以被强制类型转换为false的值:

  • undefined
  • null
  • false
  • +0、-0和NaN
  • “”
    以上称为假值假值的布尔强制转换类型为false。所有的对象都是真值,逻辑上讲假值列表以外的值都是真值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var a = new Boolean(false);
    var b = new Number(0);
    var c = new String("");
    Boolean(a); // true
    Boolean(b; // true
    Boolean(c); // true

    var d = [];
    var e = {};
    var f = function(){};
    Boolean(d); // true
    Boolean(e); // true
    Boolean(f); // true
    // []、{}、function(){}都不在假值列表中,所以他们都是真值

以上介绍了将值转换为stringnumberboolean的规则,接下来详细说明一下强制类型转换。

显式强制类型转换

字符串与数字之间的显式转换

字符串和数字之间是通过String()Number()这两个内建函数来是实现的,他们前面没有new关键字,并不创建对象。

1
2
3
4
5
6
7
var a = 42;
var b = String(a);
var c = '3.14';
var d = Number(c);

b; // '42'
d; // 3.14

除了String()Number()以外,还有其他方法可以实现转换:

1
2
3
4
5
6
7
8
var a = 42;
var b = a.toString();

var c = 3.14;
var d = +c;

b; // '42';
d; // 3.14

a.toString()是显式的,不过其中涉及隐式转换,因为toString()对42这样的基本类型不适用,所以js会自动为42创建一个封装对象,然后对该对象调用toString();
+运算符显式的将c转换为数字,一元运算+被普遍认为是显式强制类型转换。
+运算符的另外一个用途是将日期对象强制转换为数字,返回时间戳:

1
2
3
4
var d = new Date("Tues, 27 Feb 2018 15:30:45 CDT");
+d; // 1519763445000

var timestamp = +new Date(); //常用来获取当前的时间戳

最好使用ES5中新加入的静态方法Date.now():

1
var timestamp = Date.now();

注:应该使用Date.now()来获取当前的时间戳,用new Date(...).getTime()来获取指定时间的时间戳。

显式解析数字字符串

解析字符串中的数字和将字符串强制类型转换为数字返回的都是数字,但解析转换之间还是有明显的区别:

1
2
3
4
5
6
7
var a = "42";
var b = "42px";
Number(a); // 42
parseInt(a); // 42

Number(b); // NaN
parseInt(b); // 42

解析允许字符串中含有非数字字符,解析按从左到右的顺序,遇到非数字字符就停止;而转换不允许出现非数字字符,否则会失败返回NaN
注:解析字符串中的浮点数可以使用parseFloat()函数。
parseInt()parseFloat()都是针对字符串值,应避免向其传递非字符串值。

显式转换为布尔值

String(..)Number(..)一样,Boolean(..)是显式的ToBoolean强制类型转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
//参考上面的假值表

虽然Boolean(..)是显式的,但不常用。与+类型,一元运算符!显式的将值强制类型转换为布尔值,但它同时还将真值转换为假值,所以显式强制类型转换为布尔值的最常用方法是!!,第二个!将结果反转:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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(a)!!a来进行显式强制类型转换。

隐式强制类型转换

隐式强制类型转换的作用是减少冗余,让代码简洁。

字符串和数字之间的隐式强制类型转换

+ 运算符既能用于数字加法,也能用于字符串拼接,如果某个操作数是字符串或者能够通过以下步骤转为字符串,+ 将进行拼接操作。即如果某个操作数是对象(包括数组),则对其调用ToPrimitive抽象操作,得到字符串,进行拼接操作。

1
2
3
4
var a = [1,2];
var b = [3,4];

a + b; // '1,23,4'

数组的valueOf()操作无法得到简单的基本类型,于是接着调用toString()操作,使用,连接各元素,返回字符串。
总结: 如果+ 的其中一个操作数是字符串(或者通过以上步骤能转为字符串),则执行字符串拼接操作,否则执行数字加法。
我们可以将数字和空字符串””相+ 来将其转换为字符串:

1
2
3
var a = 42;
var b = a + '';
b; // "42"

a + '' 会对a调用valueOf()方法,然后通过ToString抽象操作将返回值转换为字符串,而String(a)则是直接调用ToString()
它们最后都返回字符串,但a如果是对象的话结果可能不一样:

1
2
3
4
5
6
7
var a = {
valueOf: function() { return 42; }
toString: function() { return 4; }
}

a + ""; // "42"
String(a); // "4"

一般不会遇到上面的情况,但是在定制valueOf()ToString()的时候要特别小心。

-*/ 运算符都只适用于数字,a-0 会将a 强制转换为数字。

1
2
3
4
var a = [3];
var b = [1];

a - b; // 2

为了执行减法运算,a和b都需要转换为数字,首先被转换为字符串(通过toString()),然后再转为数字。
b = String(a) (显式)和 b = a + “” (隐式)各有优点,b = a + “” 更常见一些。

隐式强制类型转换为布尔值

下面的情况会发生布尔值隐式强制类型转换:

  • if(..)语句中的条件判断表达式。
  • for(..; ..; ..;)语句中的条件判断表达式(第二个)。
  • while(..)和 do..while(..)循环中的条件判断表达式。
  • ? : 三元运算中的条件判断表达式。
  • 逻辑运算符 || 和 && 左边的操作数(作为条件判断表达式)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var a = '42';
    var b = 'abc';
    var c;
    var d = null;

    if(a){
    console.log('yep'); // yep
    }
    while(c){
    console.log('nope, never runs');
    }

    c = d ? a : b;
    c; // 'abc'

    if((a && b) || c){
    console.log('yep'); // yep
    }