手写一个parseInt——Number类型转换详解

1,514 阅读3分钟

在手写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方法的结果进行对照。

测试了一下似乎没什么大问题,但是,并没有完!还忽略了一个很重要的部分——科学计数法!下次再补!