Lodash源码阅读-stringToPath

Lodash 源码阅读-stringToPath

概述

stringToPath 是 Lodash 内部的一个小工具函数,它的作用很直接:把字符串形式的属性路径(比如 'a.b.c''a[0].b')转换成数组形式(比如 ['a', 'b', 'c']['a', '0', 'b'])。这个函数是 Lodash 中处理嵌套对象属性访问的基础,让我们可以用字符串来描述如何层层深入一个对象。

前置学习

依赖函数

  • memoizeCapped:带缓存上限的记忆化函数,防止缓存过大占用过多内存
  • reEscapeChar:一个正则表达式,用于处理转义字符

技术知识

  • 属性路径表示法 :JavaScript 中用点号(.)或方括号([])访问对象属性的语法
  • 正则表达式 :用于匹配字符串中复杂模式的工具,这里用 rePropName 解析路径
  • 记忆化技术:通过缓存函数结果提高重复调用的性能
  • 字符编码 :使用 charCodeAt 方法判断字符的 ASCII 码值

源码实现

js 复制代码
var stringToPath = memoizeCapped(function (string) {
  var result = [];
  if (string.charCodeAt(0) === 46 /* . */) {
    result.push("");
  }
  string.replace(rePropName, function (match, number, quote, subString) {
    result.push(
      quote ? subString.replace(reEscapeChar, "$1") : number || match
    );
  });
  return result;
});

实现思路

stringToPath 的实现其实挺简单:先创建一个空数组用来存放解析结果,然后检查输入字符串是否以点号开头,如果是(比如 .name),就先放一个空字符串表示从根对象开始。接着用一个精心设计的正则表达式去匹配路径中的每一段,可能是普通属性名(如 name)、数字索引(如 [0] 中的 0)或带引号的属性名(如 ["foo"] 中的 foo)。找到一段就往结果数组里推一个,最后返回整个数组。整个函数还用 memoizeCapped 包了一层,这样对相同的输入字符串就能直接返回之前的结果,不用重复解析,提高性能。

源码解析

记忆化处理

js 复制代码
var stringToPath = memoizeCapped(function (string) {
  // 函数体...
});

这里用 memoizeCapped 包装了整个函数,这样做有什么好处?

  1. 性能提升:相同的路径字符串只解析一次,后续直接用缓存的结果
  2. 内存保护memoizeCapped 会限制最多缓存 500 个结果,避免内存泄漏

想象一下,如果你的程序中经常需要访问 obj.user.profile.name,每次都从头解析 "user.profile.name" 就很浪费。有了缓存,第一次解析后,后续直接返回 ["user", "profile", "name"]

处理以点号开头的路径

js 复制代码
if (string.charCodeAt(0) === 46 /* . */) {
  result.push("");
}

为什么要检查第一个字符是不是点号?因为如果路径以点号开头(如 .name),表示从当前对象开始,需要在结果数组中添加一个空字符串作为第一个元素。

js 复制代码
// 例如:
stringToPath(".name"); // 返回 ['', 'name']

这里用 charCodeAt(0) === 46 而不是 string[0] === '.',是为了提高性能,直接比较字符的 ASCII 码值更快。

使用正则表达式解析各部分

js 复制代码
string.replace(rePropName, function (match, number, quote, subString) {
  result.push(quote ? subString.replace(reEscapeChar, "$1") : number || match);
});

这段代码是整个函数的核心,它利用正则表达式的 replace 方法来遍历并解析路径中的每一段。看起来复杂,我们拆开来看:

  1. string.replace 并不是真的要替换什么,而是利用它的回调函数来处理匹配到的每一段
  2. rePropName 正则表达式能匹配三种路径形式:
    • 普通属性名:如 userprofile
    • 数字索引:如 [0] 中的 0
    • 带引号的属性名:如 ["foo"]['bar'] 中的 foobar
  3. 回调函数接收四个参数:
    • match:匹配到的完整文本
    • number:如果是数字索引,这里就是数字部分
    • quote:如果是带引号的属性名,这里是引号字符
    • subString:如果是带引号的属性名,这里是引号中间的内容

举个例子:

js 复制代码
// 路径 "a[0]['b.c']"
// 第一次匹配: match="a", number=undefined, quote=undefined, subString=undefined
// 第二次匹配: match="[0]", number="0", quote=undefined, subString=undefined
// 第三次匹配: match="['b.c']", number=undefined, quote="'", subString="b.c"

// 结果数组: ["a", "0", "b.c"]

对于带引号的属性名,还需要处理可能存在的转义字符(如 \"\'),这就是 subString.replace(reEscapeChar, '$1') 的作用。

返回最终结果

js 复制代码
return result;

最后返回填充好的结果数组,表示解析完成的属性路径。

总结

stringToPath 虽然是个小函数,但它在 Lodash 中扮演着重要角色,是属性访问相关功能的基础。它的设计体现了几个关键点:

  1. 实用性:解决了 JavaScript 中访问嵌套属性的常见问题
  2. 性能优化:通过记忆化缓存避免重复解析相同的路径
  3. 鲁棒性:能处理各种形式的属性路径表示法
  4. 内部实现复用:作为基础工具函数被多个 Lodash 方法使用

如果你经常处理复杂的嵌套对象,可以考虑学习 stringToPath 的实现思路,或直接使用 Lodash 提供的 _.get_.set_.has 这些基于它构建的高级功能。

相关推荐
还是鼠鼠1 小时前
Node.js自定义中间件
javascript·vscode·中间件·node.js·json·express
大莲芒4 小时前
react 15-16-17-18各版本的核心区别、底层原理及演进逻辑的深度解析--react17
前端·react.js·前端框架
木木黄木木6 小时前
html5炫酷3D文字效果项目开发实践
前端·3d·html5
Li_Ning216 小时前
【接口重复请求】axios通过AbortController解决页面切换过快,接口重复请求问题
前端
胡八一7 小时前
Window调试 ios 的 Safari 浏览器
前端·ios·safari
Dontla7 小时前
前端页面鼠标移动监控(鼠标运动、鼠标监控)鼠标节流处理、throttle、限制触发频率(setTimeout、clearInterval)
前端·javascript
再学一点就睡7 小时前
深拷贝与浅拷贝:代码世界里的永恒与瞬间
前端·javascript
CrimsonHu7 小时前
B站首页的 Banner 这么好看,我用原生 JS + 三大框架统统给你复刻一遍!
前端·javascript·css
Enti7c7 小时前
前端表单输入框验证
前端·javascript·jquery
拉不动的猪8 小时前
几种比较实用的指令举例
前端·javascript·面试