
1.2.2 Number类型转换
在实际的开发中,我们经常会遇到将其他类型的值转换为Number类型的情况。在JavaScript中,一共有3个函数可以完成这种转换,分别是Number()函数、parseInt()函数、parseFloat()函数,接下来就详细地讲解3个函数的使用方法与注意事项。
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
③ 如果值为null,则返回“0”。
Number(null); // 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类型的示例。
ar 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点。
(1)非字符串类型转换为字符串类型
如果遇到传入的参数是非字符串类型的情况,则需要将其优先转换成字符串类型,即使传入的是整型数据。
parseInt('0x12', 16); // 18 parseInt(0x12, 16); // 24
第一条语句直接将字符串"0x12"转换为十六进制数,得到的结果为1×16+2=18;
第二条语句由于传入的是十六进制数,所以会先转换成十进制数18,然后转换成字符串"18",再将字符串"18"转换成十六进制数,得到的结果为1×16+8=24。
(2)数据截取的前置匹配原则
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,直接当作字符串处理,并不会进行乘法运算
(3)对包含字符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"转换为十六进制的数,得到的结果是6×163 = 24576。
第三条语句parseInt('6e3', 10),表示的是将字符串'6e3'转换为十进制的整数,因为字符'e'不在十进制所能表达的范围内,所以会直接省略,实际处理的字符串只有"6",得到的结果为6。
第四条语句parseInt('6e3', 16),表示的是将字符串'6e3'转换为十六进制的整数,因为字符'e'在十六进制所能表达的范围内,所以会转换为14进行计算,最后得到的结果为6×162 +14×16 + 3 = 1763。
(4)对浮点型数的处理
如果传入的值是浮点型数,则会忽略小数点及后面的数,直接取整。
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
(5)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处理为二进制表示,实际上二进制时只有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”。
与parseInt()函数相比,parseFloat()函数没有进制的概念,所以在转换时会相对简单些,但是仍有以下一些需要注意的地方。
① 如果在解析过程中遇到了正负号(+ / -)、数字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,'+'出现在第二位,不会当作符号位处理
② 字符串前面的空白符会直接忽略,如果第一个字符就无法解析,则会直接返回“NaN”。
parseFloat(' 1.2'); // 1.2 parseFloat('f1.2'); // NaN
③ 对于字符串中出现的合法科学运算符e,进行运算处理后会转换成浮点型数,这点与parseInt()函数的处理有很大的不同。
parseFloat('4e3'); // 4000 parseInt('4e3', 10); // 4
parseFloat()函数在处理'4e3'时,会先进行科学计数法的运算,即4e3 = 4×1000 = 4000,然后转换成浮点型数,返回“4000”;
parseInt()函数在以十进制处理'4e3'时,不会进行科学计数法的运算,而是直接从第一个字符开始匹配,最终匹配成功的字符为'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()函数在解析时会依赖于传入的基数做数值转换。