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 的情况。

相关推荐
CCF_NOI.1 小时前
谷歌浏览器深入用法全解析:解锁高效网络之旅
大数据·运维·服务器·前端·计算机·谷歌
paopaokaka_luck4 小时前
基于SpringBoot+Uniapp的健身饮食小程序(协同过滤算法、地图组件)
前端·javascript·vue.js·spring boot·后端·小程序·uni-app
患得患失9494 小时前
【前端】【vscode】【.vscode/settings.json】为单个项目配置自动格式化和开发环境
前端·vscode·json
飛_5 小时前
解决VSCode无法加载Json架构问题
java·服务器·前端
YGY Webgis糕手之路7 小时前
OpenLayers 综合案例-轨迹回放
前端·经验分享·笔记·vue·web
90后的晨仔7 小时前
🚨XSS 攻击全解:什么是跨站脚本攻击?前端如何防御?
前端·vue.js
Ares-Wang7 小时前
JavaScript》》JS》 Var、Let、Const 大总结
开发语言·前端·javascript
90后的晨仔7 小时前
Vue 模板语法完全指南:从插值表达式到动态指令,彻底搞懂 Vue 模板语言
前端·vue.js
德育处主任8 小时前
p5.js 正方形square的基础用法
前端·数据可视化·canvas