JavaScript类型与类型转换

引子

ECMAScript的变量是松散类型的,所谓松散类型就是可以用来保存任何类型的数据。换句话说,每个变量仅仅是一个用于保存值的占位符而已。

Nicolas C.Zakas --JavaScript高级程序设计

由于JavaScript是一种松散类型的语言,即变量在使用时,并不需要事先知道它的类型。因此不同变量间的比较往往要作类型转换,这也是一些常见quiz的由来。
比如下面的一道面试题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//请写出下面语句的输出结果
if ([]) console.log(1); // 1
if ({}) console.log(2); // 2
if ([] == true) console.log(3); // 无
if ({} == true) console.log(4); // 无
if (null == undefined) console.log(5) // 5
if (NaN == NaN) console.log(6) // 无
if ("5" == 5) console.log(7) // 7
//下面的结果你能写出来么
console.log([] + {}); // "[object Object]"
console.log({} + []); // "[object Object]"
console.log({} - []); // -0
console.log([] - {}); // NaN
console.log([] + []); // ""
console.log([] - []); // 0
console.log({} + {}); // "[object Object][object Object]"
console.log({} - {}); // NaN
//下面的呢
typeof null // "object"
typeof function () {} // "function"
[] instanceof Array // "true"
{} instanceof Object // "true"

怎么样?是不是有点晕,下面我们一部分一部分地来解释JavaScript中一些类型和相等相关的“潜规则”。

数据类型

让我们先从JavaScript的数据类型开始。JavaScript中只有5种基本类型和引用类型。其中5种基本类型分别是:

  • Undefined
  • Null
  • Number
  • Boolean
  • String

除此之外只有1种引用类型——Object,Object本质上是由一组无序的键值对组成。5种基本类型是按值访问的,引用类型Object是按引用访问的。

可以使用typeof操作符监测变量的基本类型。*它可以判断变量是否为除null的其他5种基本类型以及function类型。除此之外都会返回”object”*。之所以null的typeof结果也为”object”,是因为null实际上表示引用指向空对象。

使用instanceof可以判断引用类型的具体值。使用方法类似于A instanceof B的形式。当B为“Object”时,表达式永远返回true。因为根据规定,所有引用类型的值都是Object的实例。

下面是几个例子。通过instanceof操作符可以很方便地区分空数组和空对象(当然还有Object.prototype.toString.call()和[].concat()两种方法。)

1
2
console.log([] instanceof Array); // true
console.log(/w+/g instanceof RegExp); // true

类型转换

to Boolean类型

Boolean类型是ECMAScript中使用最多的类型之一。类型只有true和false两个字面量。true不一定等于1,false也不一定等于0.可以通过调用Boolean()函数将其他类型转型为Boolean类型。规则如下:

  • String类型:非空字符串=>true,空字符串=>false
  • Number类型:非零数字(包括Inifity)=>true, 0和NaN=>false
  • Object类型:任何对象=>true, null=>false
  • Undefined:false

在使用if()语句或三元操作符等情况要求Boolean类型时,括号内的表达式将会自动使用Boolean()函数转换为布尔类型。

to String类型

有两种方法可以将值转为字符串,一种是使用几乎所有值都有的toString方法,对于null和undefined使用另一种——String()函数。

前者适用于除null和undefined外的所有值,甚至String本身(返回一个自身的副本)。有些toString()方法接收一个基数作为参数(如Number)对Object使用toString方法时,会根据对象内toString的定义决定。

  • Array返回逗号隔开的不包括外侧中括号的字符串
  • Function返回Function定义的字符串
  • 普通Object返回”[object Object]”
  • null和undefined分别返回”null”和”undefined”

to Number类型

可以使用Number(), parseInt()和parseFloat()三个函数做强制转换。转换到Number类型的规则要更好理解些。

  • 是Boolean类型时,true和false分别转换到1和0
  • 数字类型时,返回本身
  • null时返回0
  • undefined时返回NaN
  • 对字符串使用类似于parseInt和parseFloat类似的方法(可以识别0x这样的进制前缀甚至Infinity这样的字符串
  • 对象使用valueOf()方法,再使用之前的规则;如果结果是NaN,再使用toString()方法作转换

类型转换场景

一元加减

一元加减只需对操作数强制转换到Number类型。向下面这样的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var s1 = "01";
var s2 = "1.1";
var s3 = "z";
var b = false;
var f = 1.1;
var o = {
valueOf: function() {
return -1;
}
}

s1 = +s1; // 1
s2 = +s2; // 1.1
s3 = -s3; // NaN
b = +b; // 0
f = +f; // 1.1
o = -o; // 1

加性操作符

ECMAScript中规定的加减法这两个操作符有一些特殊行为,不仅处理数值的加减,还处理字符串的加减。因此转换规则还有些复杂。

加法

优先做数值加减,无法完成时做字符串拼接。两个操作数都是数值时,执行常规的加法计算。

  • 一个操作数为NaN时,返回NaN
  • Inifity + -Inifity,返回NaN
  • +0 加 -0,返回+0

只要有一个操作数为字符串类型,应用下面规则:

  • 两个都是字符串时,则将它们拼接起来。
  • 一个是字符串时,先将另一个转换为字符串

布尔值和null以及undefined在另一个操作数是数值类型时转换为数值类型,反之转换为字符串类型
一个操作数为对象时,转换为字符串类型

减法

与加法类似,除了数值相减减法也需要做一些类型转换。但是和加法不一样的是,减法返回的一定是Number类型

  • 一个数值为NaN时,结果为NaN
  • 同号的Infinity相减返回NaN(如Infinity - Infinity),异号的Infinity相减等于第一个操作数
  • 除了-0减+0返回-0,其余0间相减均返回+0
  • 操作数出现字符串、布尔值、null、undefined时,做Number转换再进行数值减法
  • 对象先尝试用valueOf方法获得对象数值,若无此方法则调用toString方法,并转换得到的字符串。

关系操作符

关系操作符即大于(>)、小于(<)、大于等于(>=)和小于等于(<=)。在操作数并非纯数值时,ECMAScript也会进行数据转换或一些奇怪的操作。

  • 两个操作数都是数值时,进行数值比较
  • 两个操作数都是字符串时,按照对应字符编码顺序比较
  • 一个操作数是数值时,转换另一个为数值再比较
  • 一个操作数是对象时,优先使用valueOf方法比较数值,没有该方法时再使用toString方法
  • 任何数和NaN比较都会返回false

相等和全等

相等和全等用于确认两个变量是否相等。对此ECMAScript提供两组操作符:-相等-和-全等-。相等先转换类型后比较,全等仅比较不转换类型。由于情况较多较复杂,这里单独列一节。

ECMAScript中相等操作符为==。不相等操作符为!=。它们都会先强制转型变量再相互比较。转换规则如下:

  • 先将布尔值转换为数值,false转换为0,true转换为1
  • 字符串数值比较时,将字符串转换为数值
  • 两个操作数都是对象时,判断它们是否指向同一个对象(只比较引用)
  • 只有一个操作数是对象时,调用valueOf()或toString()方法获得基本类型值
  • nullundefined是相等的
  • nullundefined在比较时不会被转换
  • NaN出现时,相等操作符返回false

全等操作符为===,对象的不全等操作符为!==。它们不会转换变量类型,相比较类型后比较值。因此行为更容易预测。