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 这些基于它构建的高级功能。

相关推荐
kingwebo'sZone5 小时前
C#使用Aspose.Words把 word转成图片
前端·c#·word
xjt_09015 小时前
基于 Vue 3 构建企业级 Web Components 组件库
前端·javascript·vue.js
我是伪码农6 小时前
Vue 2.3
前端·javascript·vue.js
夜郎king6 小时前
HTML5 SVG 实现日出日落动画与实时天气可视化
前端·html5·svg 日出日落
辰风沐阳6 小时前
JavaScript 的宏任务和微任务
javascript
夏幻灵7 小时前
HTML5里最常用的十大标签
前端·html·html5
冰暮流星7 小时前
javascript之二重循环练习
开发语言·javascript·数据库
Mr Xu_7 小时前
Vue 3 中 watch 的使用详解:监听响应式数据变化的利器
前端·javascript·vue.js
未来龙皇小蓝7 小时前
RBAC前端架构-01:项目初始化
前端·架构
程序员agions8 小时前
2026年,微前端终于“死“了
前端·状态模式