Lodash源码阅读-toNumber

功能概述

toNumber 函数是 Lodash 中用于将各种类型的值转换为数字的工具函数。它能处理多种输入类型,包括原始数字、Symbol、对象、字符串等,并尝试将它们转换为有效的数字。如果转换失败,则返回 NaN。

源码实现

js 复制代码
function toNumber(value) {
  if (typeof value == "number") {
    return value;
  }
  if (isSymbol(value)) {
    return NAN;
  }
  if (isObject(value)) {
    var other = typeof value.valueOf == "function" ? value.valueOf() : value;
    value = isObject(other) ? other + "" : other;
  }
  if (typeof value != "string") {
    return value === 0 ? value : +value;
  }
  value = baseTrim(value);
  var isBinary = reIsBinary.test(value);
  return isBinary || reIsOctal.test(value)
    ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
    : reIsBadHex.test(value)
    ? NAN
    : +value;
}

var NAN = 0 / 0;
var reIsBinary = /^0b[01]+$/i;
var reIsOctal = /^0o[0-7]+$/i;
var freeParseFloat = parseFloat,
  freeParseInt = parseInt;
var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;

实现原理解析

整体思路

toNumber 函数采用了一种逐步判断和转换的策略,按照不同的数据类型采用相应的转换方法。主要处理以下几种情况:

  1. 原始数字类型直接返回
  2. Symbol 类型返回 NaN
  3. 对象类型尝试转换为原始值
  4. 字符串类型处理特殊格式(二进制、八进制、十六进制)

让我们详细看看每个判断条件:

1. typeof value == 'number'

处理原始数字类型,这是最简单的情况。

js 复制代码
_.toNumber(42); // => 42
_.toNumber(3.14); // => 3.14
_.toNumber(-0); // => -0
_.toNumber(Infinity); // => Infinity

2. isSymbol(value)

处理 Symbol 类型,由于 Symbol 不能转换为数字,统一返回 NaN。

js 复制代码
_.toNumber(Symbol("123")); // => NaN
_.toNumber(Symbol.iterator); // => NaN

3. isObject(value)

处理对象类型,这里采用了两步转换策略:

  1. 先尝试调用对象的 valueOf 方法
  2. 如果结果还是对象,则转换为字符串

让我们逐行分析对象转换的代码:

js 复制代码
if (isObject(value)) {
  // 第一步:尝试获取原始值
  var other = typeof value.valueOf == "function" ? value.valueOf() : value;
  // 第二步:如果还是对象则转字符串,否则使用原始值
  value = isObject(other) ? other + "" : other;
}

代码解读:

  1. if (isObject(value)) - 首先判断是否为对象类型

    • 通过 isObject 函数判断,该函数会检查 typeof 结果是否为 'object' 或 'function',并且值不为 null
    • 这样可以捕获普通对象、数组、函数等复杂类型
  2. typeof value.valueOf == "function" ? value.valueOf() : value

    • 检查对象是否有 valueOf 方法
    • valueOf 是 JavaScript 内置的对象方法,用于获取对象的原始值
    • 如果 valueOf 方法存在,则调用它尝试获取原始值
    • 如果不存在,则保持原对象不变
  3. value = isObject(other) ? other + "" : other

    • 检查 valueOf 的返回值是否还是对象
    • 如果是对象,则通过 other + "" 强制转换为字符串
    • 如果不是对象,则直接使用 valueOf 的返回值
    • 这里的 + "" 操作会隐式调用对象的 toString 方法

示例分析:

js 复制代码
// 使用 valueOf
_.toNumber(new Number(123)); // => 123
// 解析:
// 1. new Number(123) 是对象,进入 isObject 分支
// 2. Number 对象的 valueOf 返回原始值 123
// 3. 123 不是对象,直接使用这个值

// 对象转字符串
var obj = {
  toString: function () {
    return "42";
  },
};
_.toNumber(obj); // => 42
// 解析:
// 1. 普通对象,进入 isObject 分支
// 2. obj.valueOf() 返回对象本身
// 3. 由于还是对象,使用 toString 方法转换为字符串 "42"
// 4. 字符串 "42" 最终被转换为数字 42

// 自定义 valueOf
var customObj = {
  valueOf: function () {
    return 99;
  },
};
_.toNumber(customObj); // => 99
// 解析:
// 1. 自定义对象,进入 isObject 分支
// 2. customObj.valueOf() 返回数字 99
// 3. 99 不是对象,直接使用这个值

这种实现方式遵循了 JavaScript 的类型转换规则,通过 valueOf 和 toString 方法的配合,确保了对象到数字的可靠转换。特别是在处理包装对象(如 new Number())和自定义对象时,能够按照预期的方式进行转换。

4. 字符串类型处理

字符串的处理是最复杂的部分,需要处理多种进制的数字表示。让我们逐行分析代码的执行过程:

js 复制代码
value = baseTrim(value);
var isBinary = reIsBinary.test(value);
return isBinary || reIsOctal.test(value)
  ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
  : reIsBadHex.test(value)
  ? NAN
  : +value;

代码解读:

  1. value = baseTrim(value)

    • 使用 baseTrim 函数去除字符串两端的空白字符
    • baseTrim 是 Lodash 的内部函数,专门用于处理字符串修剪
    • 例如:" 123 " 会被处理成 "123"
  2. var isBinary = reIsBinary.test(value)

    • 使用正则表达式 /^0b[01]+$/i 检测是否为二进制字符串
    • ^0b 匹配以 "0b" 或 "0B" 开头
    • [01]+ 匹配一个或多个 0 或 1
    • $ 确保匹配到字符串末尾
    • i 标志使匹配大小写不敏感
  3. isBinary || reIsOctal.test(value)

    • 检查是否为二进制或八进制字符串
    • 八进制使用正则 /^0o[0-7]+$/i 检测
    • ^0o 匹配以 "0o" 或 "0O" 开头
    • [0-7]+ 匹配一个或多个 0-7 的数字
  4. freeParseInt(value.slice(2), isBinary ? 2 : 8)

    • 如果是二进制或八进制,截取掉前缀("0b" 或 "0o")
    • 根据进制类型选择基数(2 或 8)
    • 使用 parseInt 将字符串转换为对应进制的数字
    • 例如:"0b101" → "101"(二进制) → 5(十进制)
  5. reIsBadHex.test(value)

    • 使用正则 /^[-+]0x[0-9a-f]+$/i 检测是否为十六进制
    • ^[-+] 匹配可选的正负号
    • 0x 匹配十六进制前缀
    • [0-9a-f]+ 匹配十六进制数字
    • 注意:Lodash 认为这是无效格式,返回 NaN
  6. +value

    • 如果不是特殊进制格式,使用一元加操作符转换
    • 可以处理普通数字字符串,如 "123"、"-3.14"
    • 无效数字字符串会返回 NaN

这种实现方式能够优雅地处理各种数字字符串格式:

js 复制代码
// 普通数字字符串
_.toNumber("123"); // => 123
_.toNumber("  3.14  "); // => 3.14

// 二进制
_.toNumber("0b101"); // => 5
_.toNumber("0B101"); // => 5

// 八进制
_.toNumber("0o7"); // => 7
_.toNumber("0O10"); // => 8

// 十六进制(无效格式返回 NaN)
_.toNumber("0x1f"); // => NaN
_.toNumber("-0x1f"); // => NaN

// 无效数字字符串
_.toNumber("abc"); // => NaN
_.toNumber(""); // => 0

总结

toNumber 是一个强大的类型转换函数,它通过层层判断和处理,能够优雅地处理各种类型的输入。特别是在处理字符串类型时,考虑了多种数字表示法,使其在实际应用中具有很强的适应性。不过需要注意的是,并非所有输入都能被成功转换为有效数字,在使用时要注意处理 NaN 的情况。

相关推荐
南棱笑笑生5 分钟前
20250302让chrome打开刚关闭的网页
前端·chrome
银之夏雪25 分钟前
ESLint 深度解析:原理、规则与插件开发实践
java·前端·javascript
白嫖叫上我1 小时前
js删除嵌套数组对象中的某项,并重置其后的索引
前端·javascript
web135085886351 小时前
【Vue教程】使用Vite快速搭建前端工程化项目 Vue3 Vite Node.js
前端·vue.js·node.js
下雨打伞干嘛1 小时前
前端怎么排查幽灵依赖
前端
yinxiangzhongqing2 小时前
从vue源码解析Vue.set()和this.$set()
前端·javascript·vue.js
廖若星辰LTY2 小时前
uniapp 解决 H5 跨域问题
前端·javascript·uni-app·html5
青红光硫化黑3 小时前
前端基础之内置指令与自定义指令
前端·javascript
青红光硫化黑3 小时前
前端基础之收集表单数据
前端·javascript
GISer_Jing3 小时前
前端埋点项目从设计到实现详解
前端·javascript·react.js