pocket-lodash icon indicating copy to clipboard operation
pocket-lodash copied to clipboard

lodash源码分析之toNumber

Open HeftyKoo opened this issue 4 years ago • 1 comments

本文为读 lodash 源码的第五十二篇,后续文章会更新到这个仓库中,欢迎 star:pocket-lodash

gitbook也会同步仓库的更新,gitbook地址:pocket-lodash

依赖

import isObject from './isObject.js'
import isSymbol from './isSymbol.js'

《lodash源码分析之isObject》

《lodash源码分析之isSymbol》

源码分析

整体源码

const NAN = 0 / 0

/** Used to match leading and trailing whitespace. */
const reTrim = /^\s+|\s+$/g

/** Used to detect bad signed hexadecimal string values. */
const reIsBadHex = /^[-+]0x[0-9a-f]+$/i

/** Used to detect binary string values. */
const reIsBinary = /^0b[01]+$/i

/** Used to detect octal string values. */
const reIsOctal = /^0o[0-7]+$/i

/** Built-in method references without a dependency on `root`. */
const freeParseInt = parseInt

function toNumber(value) {
  if (typeof value === 'number') {
    return value
  }
  if (isSymbol(value)) {
    return NAN
  }
  if (isObject(value)) {
    const other = typeof value.valueOf === 'function' ? value.valueOf() : value
    value = isObject(other) ? `${other}` : other
  }
  if (typeof value !== 'string') {
    return value === 0 ? value : +value
  }
  value = value.replace(reTrim, '')
  const isBinary = reIsBinary.test(value)
  return (isBinary || reIsOctal.test(value))
    ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
    : (reIsBadHex.test(value) ? NAN : +value)
}

几个常量

const NAN = 0 / 0

/** Used to match leading and trailing whitespace. */
const reTrim = /^\s+|\s+$/g

/** Used to detect bad signed hexadecimal string values. */
const reIsBadHex = /^[-+]0x[0-9a-f]+$/i

/** Used to detect binary string values. */
const reIsBinary = /^0b[01]+$/i

/** Used to detect octal string values. */
const reIsOctal = /^0o[0-7]+$/i

/** Built-in method references without a dependency on `root`. */
const freeParseInt = parseInt

NAN

NaN 的意思是 not a number ,用 0 / 0 可以返回一个 NaN 值。

reTrim

这个正则用来去除前后的空格

reIsBadHex

这个正则咋看起来有点奇怪,是检测形似带符号的16进制字符串,如: +0x1f 。为什么是 bad 呢,从 lodashpr来看,应该是跟 node v0.8 的某个 bug 有关。因为 lodash 也支持在 node ,因此需要做兼容。所以十六进制的处理跟二进制和八进制不太一样。

resIsBinary

形似二进制的字符串正则,二进制以 0b 开头,后面只能是 0 或者 1

reIsOctal

形似八进制的字符串正则,八进制以 0o 开头,后面只能跟 0-7 的数字。

处理number类型

if (typeof value === 'number') {
  return value
}

如果为 number 类型,则不需要做转换,原样返回即可。

处理Symbol类型

if (isSymbol(value)) {
  return NAN
}

如果为 Symbol 类型,则直接返回 NAN

处理Object类型

if (isObject(value)) {
  const other = typeof value.valueOf === 'function' ? value.valueOf() : value
  value = isObject(other) ? `${other}` : other
}

规范中有规定,object 类型有 valueOf 的方法,这个方法可以返回对象的原始值,默认情况下,如果 valueOf 没有返回原始值,则会返回对象本身。

例如 Number(1)valueOf 会返回数字 1 。以下的例子会返回 a

const obj = {
  a: 'a',
  valueOf () {
    return 'a'
  }
}

因此,首先判断 value 有没有存在 valueOf 函数,如果有,则先调用 valueOf 得出结果。

但是,value 可能会没有 valueOf 函数,或者 valueOf 函数也可能会返回一个对象,这时,需要将 other 转换成 string 类型,留待后面处理。

处理非String类型

if (typeof value !== 'string') {
  return value === 0 ? value : +value
}

在处理完 Object 类型后,现在 value 都是基本类型了,在这个阶段,本来可以直接用 +value 来转换成 number 类型的,但是字符串前后可能会有空格,直接用 +value 来转换的话,会得出 NaN 值,因此字符串需要单独处理。

处理String类型

value = value.replace(reTrim, '')
const isBinary = reIsBinary.test(value)
return (isBinary || reIsOctal.test(value))
  ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
: (reIsBadHex.test(value) ? NAN : +value)

第一步先将 value 的前后空格移除。

接下来处理二进制和八进制,直接用 parseInt 来做转换,parseInt 的第二个参数来指定进制,其实二进制、八进制也可以用 +value 来转换的,有也人提了 pr,而且性能更好。

本来 16 进制也可以用 parseInt 来处理的,但是根据 lodash 的提交来推测,用 parseInt 来转换形似 +0x16-0x16 的字符串时,会有bug,这样的字符串,预期应该返回 NAN

如果不是形似二进制、八进制或带符号的十六进制字符串,则使用 +value 来做转换。

参考资料

MDN: Object.prototype.valueOf()

License

署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0)

最后,所有文章都会同步发送到微信公众号上,欢迎关注,欢迎提意见:

作者:对角另一面

HeftyKoo avatar Dec 16 '19 04:12 HeftyKoo

处理object类型那个if能举个例子吗,貌似这段删掉对最后结果也不影响?

Yh1404 avatar Jul 13 '21 08:07 Yh1404