前端文章翻译

在JavaScript中字符串转数字

2019-03-09  本文已影响0人  Rolker

在JavaScript中字符串转数字

在JavaScript的世界里,字符串转数字是十分微妙的。面对NaN隐式基数(指使用原生JS parseInt()等方法转换字符串形式的数字而不指定进制时,默认的进制选择规则。译者注)、数字字面量和Number对象的种种概念和用法,稍不留神就会掉进自己挖的坑里。这篇文章里,我会讲述在转换成数字时,对用parseFloat() 还是 Number() ,用Number.isNaN() 还是isNaN()之间怎么作出权衡。

另外,本文也会描述怎么用eslint来配置实行这些规则。

简单来讲,将一般的JavaScript的值转换为数字时,如果对被转换值的范围支持较为宽泛,那就应该用 Number(x),否则,用 parseFloat(x) 。在检查是否转换成功时,不要用全局对象的isNaN() 函数, 而应该用Number.isNaN()

typeof parseFloat('42'); // 'number'
Number.isNaN(Number('42')); // false

typeof parseFloat('fail'); // 'number'
Number.isNaN(Number('fail')); // true

用Number(x)做转换时,一些边界情况是否合理,取决于你如何处理它们。你也可以用类似 archetype 的工具来帮你处理一些边界值:

archetype.to('42', 'number'); // 42
// 对于空字符串的处理:
Number(''); // 0
archetype.to('', 'number'); // throws, 抛出异常

许多开发人员用 +x 来将字符串转换成数字。JavaScript语言规范中规定+xNumber(x)等价

+'42 fail'; // NaN
+({ valueOf: () => '42' }); // 42
+({ toString: () => '42' }); // 42
+(null); // 0
+('  '); // 0

Number(x)有何不妥

Number(x)parseFloat(x) 处理边界值的方式有很大差异。 parseFloat() 在处理某些字符串时,更为宽泛(能成功转换成数字):

Number('42 fail'); // NaN
parseFloat('42 fail'); // 42
parseInt('42 fail'); // 42

Number(' 10'); // 10
parseFloat(' 10'); // 10
parseInt(' 10'); // 10

你可能会误以为这就意味着用 Number(x) 处理边界值时更安全、更严格。不幸的是,处理空格、null和其他边界值的时候,Number(x)却相对没那么严格——很多值会被出乎意料地转换成0。举个几个栗子:

Number(null); // 0
Number(''); // 0
Number('    '); // 0
Number(false); // 0
Number({ toString: () => '' }); // 0
Number({ valueOf: () => '  ' }); // 0

这是因为JavaScript语言规范对于将不同的值转换成数字有一套相当复杂的规则parseFloat() 进行值转换的规则相对简单。解析引擎必须先将传入的值转换成字符串,去掉首尾空格,然后找出符合JavaScript正则表达式定义中的数字字面量的最长前置序列。

Number.isNaN()isNaN()

还有另外一个关于将不同的值转换成数字的小坑,当尝试转换成数字失败的时候,JavaScript并不会抛出异常,而会返回一个特殊的值NaN。更让人头大的是,用 typeof 运算符判断其类型时,得到的结果是'number'

Number('fail'); // NaN
typeof Number('fail'); // number

之所以有 Number.isNaN()isNaN() 这两个东东,是因为=====用来判断值相等时,如果任意一边存在NaN,得出的结果都出乎意料。

Number('fail') == Number('fail'); // false
Number('fail') === Number('fail'); // false
Number('fail') == NaN; // false
NaN === NaN; // false

Number.isNaN()是ES6的一个新特性,然而它没得到太多关注。Number.isNaN() 的鲁棒性更佳,你应该它用来替代isNaN() ,确实需要用到 isNaN() 的场合除外。

// 判断一个值是否数字,用 `=== NaN` ***行不通***,所以需要用函数来判断
isNaN(Number('fail')); // true
Number.isNaN(Number('fail')); // true

要便于区分两者,可以这么类比: Number.isNaN() 之于isNaN() 类似于=== 之于 ==isNaN() 函数在检查给定的值是否为NaN之前会先将其转换成数字

isNaN('fail'); // true
isNaN({}); // true

Number.isNaN('fail'); // false
Number.isNaN({}); // false

另一方面, 如果 x 不是数字型的值,Number.isNaN(x) 则返回 false。你可以用以下函数写一个 Number.isNaN() 的polyfill:

Number.isNaN = function(x) {
  return typeof x === 'number' && isNaN(x);
};

反过来看,isNaN(x)Number.isNaN(Number(x))等价。当你检查 Number(x)parseFloat(x) 的结果是否为 NaN 的时候, 用isNaN(x) 是安全的。这是因为传入该函数的值已经是尝试转换为数字的结果。但通常来说,应该多用Number.isNaN() 而不是 isNaN(),就跟除了真的需要用 == 来判断两值相等,你一般情况下会用 === 做判断的道理一样。

ESLint 规则

通过 eslintno-restricted-globals 规则,可配置强制使用 Number.isNaN() ,以及配置选用 Number()还是 parseFloat() 。在这个GitHub issue可查看更多信息。以下的例子是在 .eslintrc.yml配置禁用全局对象的isNaN()parseFloat() 方法。

rules:
  no-restricted-globals:
    - error
    - name: isNaN
      message: Use `Number.isNaN()` instead
    - name: parseFloat
      message: Use `Number()` instead

如果要开启parseFloat() ,禁用 Number() 的话要麻烦一丢丢,可以用no-restricted-syntax这条eslint规则来搞定。

rules:
  no-restricted-globals:
    - error
    - name: isNaN
      message: Use `Number.isNaN()` instead
  no-restricted-syntax:
    - error
    - selector: CallExpression[callee.name='Number']
      message: Do not use `Number()`, use `parseFloat()` instead

加以实践

在JavaScript中转数字,充满着奇奇怪怪的边界用例。如果你不想考虑这些情况,用 parseFloat()Number.isNaN()的组合就最合适了。想灵活处理的话,就用Number()。个人而言,因为无需检查转换结果是否为NaN,我选择 archetype

原文链接:http://thecodebarbarian.com/convert-a-string-to-a-number-in-javascript.html

原文作者:Valeri Karpov

发表时间:2019年01月22日

上一篇下一篇

猜你喜欢

热点阅读