JavaScript重难点实例精讲
上QQ阅读APP看书,第一时间看更新

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()函数在解析时会依赖于传入的基数做数值转换。