1、Number类型介绍

在JavaScript中,Number类型的数据即包括了整型数据,也包括了浮点型数据。
下面讲解整型数据的处理规则。最基本的数值采用的是十进制整数,另外,数值还可以通过八进制或者十六进制表示。

八进制:如果想要用八进制表示一个数值,那么首位必须是0,其他位必须是0~7的八进制序列。如果后面位数的字面值大于7,则破坏了八进制数据表示规则,前面的0会被忽略,当作十进制数据处理。

var num1 = 024; //20
var num2 = 079; //79

其中num1首位为0,表示八进制数,然后判断后面每位数值在0~7内,符合八进制数据表示规则,最后将其转换为十进制数值2*8+4=20.
num2首位为0,表示八进制数,然后判断后面每位的数值,最后一位9超出了八进制字面值,所以不属于八进制数据,最终按照十进制处理,结果为79.

十六进制:如果想要用十六进制表示一个数值,那么前两位必须是0x,其他位必须是十六进制序列(0-9,a-f或者A-F)。如果超过了十六进制序列,则会抛出异常。

var num3 = 0x3f; //63
var num4 = 0x2g; // SyntaxError: Invalid or unexpected token

其中num3前两位为0x,表示十六进制数据,然后判断后面两位均属于十六进制字面区间,符合十六进制数表示,最后将其转换为十进制数值3*16+15=63。

num4前两位为0x,表示十六进制数据,然后判断后面每位数值,最后一位g超出了十六进制所能表示的字面值区间,所以不满足十六进制数据表示规则,最终抛出异常“SyntaxError:Invalid or unexpected token”。

此外,和Boolean类型一样,当其他类型在与Number类型进行数据转换时,也会遵循一定的规则。

(1)Boolean类型转换为Number类型

  • true转换为1
  • false转换为0

(2)Null类型转换为Number类型

  • Null类型只有一个字面值null,直接转换为0

(3)Undefined类型转换为Number类型

  • Undefined类型只有一个字面值undefined,直接转换为NaN

(4)String类型转换为Number类型

  • 如果字符串中只包含数字,则会转换为十进制数;如果前面有-,会直接省略掉,例如“0123”会转换为123。
  • 如果字符串中包含的是有效的浮点数,则同样按照十进制转换,例如“1.23”会转换为1.23。
  • 如果字符串中包含有效的十六进制格式,则会按照十进制转换,例如“0x3f”会转换为63。
  • 如果是空字符串,则转换为0。
  • 如果字符串中包含了除上述格式以外的字符串,则会直接转换为NaN。

(5)Object类型转换为Number类型

  • Object类型在转换为Number类型时,会优先调用valueOf()函数,然后通过valueOf()函数的返回值按照上述规则进行转换。如果转换的结果是NaN,则调用toString()函数,通过toString()函数的返回值重新按照上述规则进行转换;如果有确定的Number类型返回值,则结束,否则返回“NaN”。

Number类型转换

在实际的开发中,我们经常会遇到将其他类型的值转换为Number类型的情况。在JavaScript中,一共有3个函数可以完成这种转换,分别是Number()函数、parseInt()函数、parseFloat()函数。

1、Number()函数

Number()函数可以用于将任何类型转换为Number类型,它在转换时遵循下列规则。

如果是数字,会按照对应的进制数据格式,统一转换为十进制并返回。
Number(10); // 10
Number(010); // 8  010是八进制的数据,转换成十进制是8
Number(0x10); // 16 0x10是十六进制数据,转换成十进制是16
如果是Boolean类型的值,true将返回为“1”,false将返回为“0”
Number(true); // 1
Number(false); // 0
如果值为undefined,则返回“NaN”
Number(undefined); // NaN
如果值为字符串类型,则遵循下列规则

如果该字符串只包含数字,则会直接转换成十进制数;如果数字前面有0,则会直接忽略这个0

Number("21"); // 21
Number("012"); // 12

如果字符串是有效的浮点数形式,则会直接转换成对应的浮点数,前置的多个重复的0会被清空,只保留一个。

Number("0.12"); // 0.12
Number("00.12"); // 0.12

如果字符串是有效的十六进制形式,则会转换为对应的十进制数值。

Number("0x12"); // 18
Number("0x21"); // 33

如果字符串是有效的八进制形式,则不会按照八进制转换,而是直接按照十进制转换并输出,因为前置的0会被直接忽略。

Number("010"); // 10
Number("0020"); // 20

如果字符串为空,即字符串不包含任何字符,或为连续多个空格,则会转换为0。

Number(""); // 0
Number(" "); // 0

如果字符串包含了任何不是以上5种情况的其他格式内容,则会返回“NaN”。

Number("123a"); // NaN
Number("a1.1"); // NaN
Number("abc"); // NaN

如果值为对象类型,则会先调用对象的valueOf()函数获取返回值,并将返回值按照上述步骤重新判断能否转换为Number类型。如果都不满足,则会电泳对象的toString()函数获取返回值,并将返回值重新按照步骤判断能否转换为Number类型。如果也不满足,则返回“NaN”。

以下是通过valueOf()函数将对象正确转换为Number类型的示例。

var obj = {
	age: 21,
	valueOf: function(){
		return this.age
	},
	toString: function(){
		return 'good'
	}
}

Number(obj); //21

以下是通过toString()函数将对象正确转换成Number类型的示例。

var obj = {
	age: '21',
	valueOf: function(){
		return [];
	},
	toString: function(){
		return this.age;
	}
}
Number(obj); // 21

以下示例是通过valueOf()函数和toString()函数都无法将对象转换成Number类型的示例(最后返回“NaN”)。

var obj = {
	age: '21',
	valueOf: function(){
		return 'a';
	},
	toString: function(){
		return 'b';
	}
}
Number(obj); // NaN

如果toString()函数和valueOf()函数返回的都是对象类型而无法转换成基本数据类型,则会抛出类型转换的异常。

var obj = {
	age: '21',
	valueOf: function(){
		return [];
	},
	toString: function(){
		return [];
	}
}
Number(obj); // 抛出异常TypeError: Cannot convert object to primitive value

2、parseInt()函数

parseInt()函数用于解析一个字符串,并返回指定的基数对应的整数值。

其语法格式如下:

parseInt(string, radix);

其中string表示要被解析的值,如果该参数不是一个字符串,那么会使用toString()函数将其转换成字符串,而字符串前面的空白符会被忽略。

radix表示的是进制转换的基数,数据范围是2-36,可以是使用频率比较高的二进制、十进制、八进制和十六进制等,默认值为10。因为对相同的数采用不同进制进行处理时可能会得到不同的结果,所以在任何情况下使用parseInt()函数时,建议都手动补充第二个表示基数的参数。

parseInt()函数会返回字符串解析后的整数值,如果该字符串无法转换成Number类型,则会返回“NaN”。

在使用parseInt()函数将字符串转换成整数时,需要注意以下5点。

非字符串类型转换成字符串类型

如果遇到传入的参数是非字符串类型的情况,则需要将其优先转换成字符串类型,即使传入的是整型数据。

parseInt('0x12', 16); //18
parseInt(0x12, 16); // 24

第一条语句直接将字符串“0x12”转换成十六进制数,得到的结果为1*16+2=18;

第二条语句由于传入的是十六进制数,所以会先转换成十进制数18,然后转换成字符串“18”,再将字符串“18”转换成十六进制数,得到的结果为1*16+8=24;

数据截取的前置匹配原则

parseInt()函数在做转换时,对于传入的字符串会采用前置匹配的原则。即从字符串的第一个字符开始匹配,如果处于基数指定的范围,则保留并继续往后匹配满足条件的字符,直到某个字符不满足基数指定的数据范围,则从该字符开始,舍弃后面的全部字符。在获取到满足条件的字符后,将这些字符转换成整数。

parseInt("fg123", 16); // 15

对于字符串’fg123‘,首先从第一个字符开始,’f’是满足十六进制的数据,因为十六进制数据范围是0-9,a-f(A-F),所以保留‘f’;然后是第二个字符’g’,它不满足十六进制数据范围,因此从第二个字符至最后一个字符全部舍弃,最终字符串只保留字符‘f’;然后将字符’f’转换成十六进制的数据,为15,因此最后返回的结果为“15”。

如果遇到的字符串是以“0x”开头的,那么在按照十六进制处理时,会计算后面满足条件的字符串;如果按照十进制处理,则会直接返回“0”。

parseInt('0x12', 16); // 18 = 16 + 2
parseInt('0x12', 10); // 0

需要注意的一点是,如果传入的字符串中涉及算术运算,则不执行,算术符号会被当作字符处理;如果传入的参数是算术运算表达式,则会先运算完成得到结果,再参与parseInt()函数的计算。

parseInt(15 * 3, 10); // 45,先运算完成得到45,再进行parseInt(45, 10)的运算
parseInt('15 * 3', 10); // 15,直接当作字符串处理,并不会进行乘法运算
对包含字符e的不同数据的处理差异

处理的数据中包含字符e时,不同进制数的处理结果有很大不同。
当传入的参数本身就是Number类型时,会将e按照科学计数法计算后转换成字符串,然后按照对应的基数转换得到最终的结果。
如果传入的字符串中直接包含e,那么并不会按照科学计数法处理,而是会判断字符e是否处在可处理的进制范围内,如果不在则直接忽略,如果在则转换成对应的进制数。

以下为几行代码以及相应的执行结果。

parseInt(6e3, 10); // 6000
parseInt(6e3, 16); // 24576
parseInt('6e3', 10); // 6
parseInt('6e3', 16); // 1763

对于上述4个不同的结果,详细解释如下:

第一条语句parseInt(6e3, 10),首先会执行6e3=6000,然后转换为字符串“6000”,实际执行的语句是parseInt(‘6000’, 10),表示的是将字符串“6000”转换为十进制的整数,得到的结果为6000。

第二条语句parseInt(6e3, 16),首先会执行6e3=6000,然后转换为字符串“6000”,实际执行的语句是parseInt(‘6000’, 16),表示的是将字符串“6000”转换为十六进制的数,得到的结果是24576。

第三条语句parseInt(‘6e3’, 10),表示的是将字符串‘6e3’转换为十进制的整数,因为字符‘e’不在十进制所能表达的范围内,所以会直接省略,实际处理的字符串只有“6”,得到的结果为6。

第四条语句parseInt(‘6e3’, 16),表示的是将字符串’6e3‘转换为十六进制的整数,因为字符’e’在十六进制所能表达的范围内,所以会转换为14进行计算,最后得到的结果为1763。

对浮点型数的处理

如果传入的值是浮点型数,则会忽略小数点及后面的数,直接取整。

parseInt('6.01', 10); //6
parseInt('6.99', 10); //6

经过上面的详细分析,我们再来看看以下语句的执行结果。以下语句都会返回“15”,这是为什么呢?

parseInt('0xF', 16); // 十六进制的F为15,返回“15”
parseInt('F', 16); // 十六进制的F为15,返回“15”
parseInt('17', 8); // 八进制的“17”, 返回结果为1*8+7 = 15
parseInt(021, 8); // 021先转换成十进制得到17,然后转换成字符串“17”,再转换成八进制,返回结果为1*8+7=15
parseInt('015', 10); // 前面的0忽略,返回“15”
parseInt(15.99, 10); // 直接取整,返回“15”
parseInt('15,123', 10); // 字符串“15,123”一一匹配,得到“15”,转换成十进制后返回“15”
parseInt('FXX123', 16); // 字符串“FXX123”一一匹配,得到“F“,转换成十六进制后返回”15“
parseInt('1111', 2); // 1*23+1*22+1*2+1 = 15
parseInt('15 * 3', 10); // 字符串中并不会进行算术运算,实际按照“15”进行计算,返回“15”
parseInt('15e2', 10); // 实际按照字符串“15”运算,返回“15”
parseInt('15px', 10); // 实际按照字符串“15”运算,返回“15”
parseInt('12', 13); // 按照十三进制计算,返回结果为1*13+2 = 15
map()函数与parseInt()函数的隐形坑

设想这样一个场景,存在一个数组,数组中的每个元素都是Number类型的字符串[‘1’, ‘2’, ‘3’, ‘4’],如果我们想要将数组中的元素全部转换成整数,我们该怎么做呢?
我们可能会想到在Array的map()函数中调用parseInt()函数,代码如下:

var arr = ['1', '2', '3', '4']
var result = arr.map(parseInt)

console.log(result)

但是在运行后,得到的结果是[1, NaN, NaN, NaN],与我们期望的结果[1, 2, 3, 4]差别很大,这是为什么呢?

其实这就是一个藏在map()函数与parseInt()函数中的隐形坑。

arr.map(parseInt);

上面的代码实际与下面的代码等效:

arr.map(function(val, index){
	return parseInt(val, index)
})

parseInt()函数接收的第二个参数实际为数组的索引值,所以实际处理过程如下:

parseInt('1', 0); // 1
parseInt('2', 1); // NaN
parseInt('3', 2); // NaN
parseInt('4', 3); // NaN

任何证书以0为基数取整时,都会返回本身,所以第一行代码会返回“1”。

第二行代码parseInt(‘2’, 1),因为parseInt()函数对应的基数只能为2-36,不满足基数的整数在处理后会返回“NaN”;

第三行代码parseInt(‘3’, 2),表示的是将3处理为2进制表示,实际上二进制时只有0和1,3超出了二进制的表示范围,无法转换,返回“NaN”;

第四行代码parseInt(‘4’, 3),与第三行类似,4无法用三进制的数据表示,返回“NaN”。

因此我们在map()函数中使用parseInt()函数时需要注意这一点,不能直接将parseInt()函数作为map()函数的参数,而是需要在map()函数的回调函数中使用,并尽量指定基数,代码如下:

var arr = ['1', '2', '3', '4'];
var result = arr.map(function(val){
	return parseInt(val, 10);
})

console.log(result); // [1, 2, 3, 4]
3、parseFloat()函数

parseFloat()函数用于解析一个字符串,返回对应的浮点数。如果给定值不能转换为数值,则会返回“NaN”。
与parseFloat()函数相比,parseFloat()函数没有进制的概念,所以在转换时会相对简单些,但是仍有以下一些需要注意的地方。

(1)如果在解析过程中遇到了正负号(+/-)、数字0-9、小数点或者科学计数法(e/E)以外的字符,则会忽略从该字符开始至结束的所有字符,然后返回当前已经解析的字符的浮点数形式。

其中,正负号必须出现在自负的第一位,而且不能连续出现。

parseFloat('+1.2'); // 1.2
parseFloat('-1.2'); // -1.2
parseFloat('++1.2'); // NaN,符号不能连续出现
parseFloat('--1.2'); // NaN,符号不能连续出现
parseFloat('1+1.2'); // 1, ‘+’出现在第二位,不会当作符号位处理

(2)字符串前面的空白符会直接忽略,如果第一个字符就无法解析,则会直接返回“NaN”。

parseFloat(' 1.2'); // 1.2
parseFloat('f1.2'); // NaN

(3)对于字符串中出现的合法科学运算符e,进行运算处理后会转换成浮点型数,这点与parseInt()函数的处理有很大的不同。

parseFloat('4e3'); // 4000
parseINt('4e3', 10); //4

parseFloat()函数在处理‘4e3’时,会先进行科学计数法的运算,即4e3=4*1000=4000,然后转换成浮点型数,返回“4000”;
parseInt()函数在以十进制处理‘4e3’时,不会进行科学计数法的运算,而是直接从第一个字符开始匹配,最终匹配成功的字符为‘4’,转换成整型后,返回整数‘4’。

(4)对于小数点,只能正确匹配第一个,第二个小数点是无效的,它后面的字符也都将被忽略。

parseFloat('11.20'); // 11.2
parseFloat('11.2.1'); // 11.2

下面是使用parseFloat()函数的综合实例。

parseFloat('123AF'); // 123, 匹配字符串‘123’
parseFloat('0xA'); // 0,匹配字符串‘0’
parseFloat('22.5'); // 22.5,匹配字符串‘22.5’
parseFloat('22.3.56'); // 22.3,匹配字符串‘22.3’
parseFloat('0908.5'); // 908.5,匹配字符串‘908.5’

4、结论

虽然Number()、parseInt()和parseFloat()函数都能用于Number类型的转换,但是他们在处理方式上还是有一些差异的。

Number()函数转换的是传入的整个值,并不是像parseInt()函数和parseFloat()函数一样会从首位开始匹配符合条件的值。如果整个值不能被完整转换,则会返回“NaN”。

parseFloat()函数在解析小数点时,会将第一个小数点当作有效字符,而parseInt()函数在解析时如果遇到小数点会直接停止,因为小数点不是整数的一部份

parseFloat()函数在解析时没有进制的概念,而parseInt()函数在解析时会依赖于传入的基数做数值转换。