在手写parseInt时需要将字符串转成整数,先来了解一下将字符串转为数字常用的两种方法:
- Number()
- parseInt(string, radix)
这两种方法看似简单,深究起来有很多门道。
Number()
Number其实就是JavaScript基本类型中数字的构造函数,接受一个参数value来创建一个Number类型的值:Number(1)
或new Number(1)
,当value为其他类型的值时,Number(value)
将会得到以下转换结果:
- 字符串:全部由数字组成则返回字符串,包含非数字的字符则返回NaN,空字符串返回0。
- Infinity和数字:原样返回。
- null / false:返回0。
- undefined:返回NaN。
parseInt
parseInt(string, radix)
方法用来将一个字符串以radix指定的几进制的形式转为十进制的整数。string表示要转换的字符串,如果不是字符串,会先转成字符串。radix表示几进制,取值范围为2~36,是个可选的参数。这个方法包含了很多容易忽略的细节:
- string前后的空格会被忽略。
- string参与转换的只有位于前面的、连续的数字部分,如
'12m'
中的'12'
,这一点与有洁癖的Number()
不同。 - string中的数字的进制不一定是10进制,是由radix参数指定的,只有符合radix指定的进制的合法数字可以参与转换。例如如果radix为2,string中只有二进制允许的数字0和1是有效的,其他的数字都是非法的。例如
0112
只有011
能转换,结果相当于二进制的11转为十进制,得到3。 Infinity/null/undefined/false
会返回NaN,这一点与Number()
不同(其实严格来说parseInt会先将这些值转为字符串),简单来说就是,parseInt只认数字。- radix不填或填0的时候默认为10,如果string是以
'0x'/'0X'
开头的,将作为16进制来解析,radix默认为16。radix超出指定范围返回NaN。 - 任何时候都应该显式指定radix的值,因为radix默认为10的表现因环境而异,不能指望!
有了以上的共识,考虑到parseInt很多的坑点,这个实现过程需要考虑很多分支情况,直接边看代码边看注释解析:
function parseIntFunc(s, radix) {
// radix取0当十进制解析,取2-36以外的数返回NaN
if (radix && (radix < 2 || radix > 36)) return NaN;
// 去除首尾空格
const str = s.toString().trim();
// 单独处理字符串为16进制的情况
const matchResult16 = str.match(/^(\-|\+)?0[xX][abcde0-9]+/);
if (matchResult16) return Number(matchResult16[0]);
// 普通情况
const matchResult = str.match(/^(\-|\+)?[0-9]+/);
if (!matchResult) return NaN;
const targetStr = matchResult[0];
if (!radix) {
// radix不传或传0按10进制解析
return Number(targetStr);
} else {
// radix在2-36范围内
// 需要考虑带符号的情况,只需要记录是不是负数最后取相反数即可
const matchResultChar = targetStr.match(/^(\-|\+)/);
let ifNegative = false;
// 先假设不带符号
let finalStr = targetStr;
if (matchResultChar) {
ifNegative = matchResultChar[0] === '-';
// 去掉符号
finalStr = targetStr.slice(1);
}
// 第一个数字不在radix进制的范围内则返回
if (Number(finalStr.slice(0, 1)) >= radix) return NaN;
let usedStr = '';
// 把最终能转换的字符一个一个提取出来,直到遇到超出radix指定进制范围内的数字
[...finalStr].some(i => {
usedStr += i;
return Number(i) >= radix;
});
// 把有用的数字字符用数组存起来
const targetArr = [...usedStr];
// 整个函数的灵魂:某进制转十进制
const targetNum = targetArr
.reverse()
.reduce((acc, cur, idx) => acc + Number(cur) * Math.pow(radix, idx), 0);
// 返回结果的时候别忘了先前带的符号!负数要计算相反数,至于为什么要用-0来减,因为有-0这种神奇的存在!至于为什么有-0,那又是另外一个故事了
return ifNegative ? -0-targetNum : targetNum;
}
}
到这里,终于可以长呼一口气了,可以添加一些用例,在控制台同时打印原生parseInt方法的结果进行对照。
测试了一下似乎没什么大问题,但是,并没有完!还忽略了一个很重要的部分——科学计数法!下次再补!