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

相关推荐
Fantastic_sj25 分钟前
CSS-in-JS 动态主题切换与首屏渲染优化
前端·javascript·css
鹦鹉00728 分钟前
SpringAOP实现
java·服务器·前端·spring
再学一点就睡4 小时前
手写 Promise 静态方法:从原理到实现
前端·javascript·面试
再学一点就睡4 小时前
前端必会:Promise 全解析,从原理到实战
前端·javascript·面试
前端工作日常5 小时前
我理解的eslint配置
前端·eslint
前端工作日常5 小时前
项目价值判断的核心标准
前端·程序员
90后的晨仔6 小时前
理解 Vue 的列表渲染:从传统 DOM 到响应式世界的演进
前端·vue.js
OEC小胖胖6 小时前
性能优化(一):时间分片(Time Slicing):让你的应用在高负载下“永不卡顿”的秘密
前端·javascript·性能优化·web
烛阴7 小时前
ABS - Rhomb
前端·webgl
植物系青年7 小时前
10+核心功能点!低代码平台实现不完全指南 🧭(下)
前端·低代码