Lodash源码阅读-SetCache

Lodash 源码阅读-SetCache

概述

SetCache 是 Lodash 中的一个内部缓存结构,专门用于存储唯一值的集合。它本质上是一个集合(Set)的实现,通过使用 MapCache 作为底层存储,为集合操作提供高效的添加和查找功能。在 Lodash 内部,SetCache 主要用于实现数组去重、交集、差集等需要高性能唯一值检查的场景。

前置学习

依赖函数

  • MapCache:SetCache 内部使用 MapCache 作为存储结构,MapCache 提供了高效的键值存储和查找能力
  • HASH_UNDEFINED :Lodash 内部的特殊标记值,用于表示值存在但是内容为 undefined

源码实现

javascript 复制代码
/**
 * Creates an array cache object to store unique values.
 *
 * @private
 * @constructor
 * @param {Array} [values] The values to cache.
 */
function SetCache(values) {
  var index = -1,
    length = values == null ? 0 : values.length;

  this.__data__ = new MapCache();
  while (++index < length) {
    this.add(values[index]);
  }
}

/**
 * Adds `value` to the array cache.
 *
 * @private
 * @name add
 * @memberOf SetCache
 * @alias push
 * @param {*} value The value to cache.
 * @returns {Object} Returns the cache instance.
 */
function setCacheAdd(value) {
  this.__data__.set(value, HASH_UNDEFINED);
  return this;
}

/**
 * Checks if `value` is in the array cache.
 *
 * @private
 * @name has
 * @memberOf SetCache
 * @param {*} value The value to search for.
 * @returns {number} Returns `true` if `value` is found, else `false`.
 */
function setCacheHas(value) {
  return this.__data__.has(value);
}

// Add methods to `SetCache`.
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
SetCache.prototype.has = setCacheHas;

实现思路

SetCache 的核心设计思路是利用 MapCache 高效的键值查找能力,将集合元素存储为 MapCache 的键,而值统一设为一个特殊的标记值(HASH_UNDEFINED)。这样实现了一个高效的集合结构,可以快速添加元素和检查元素是否存在。SetCache 只提供了两个核心操作:添加元素(add/push)和检查元素是否存在(has),这也符合集合结构的基本功能需求。整个设计专注于优化集合操作的性能,尤其是处理大量数据时的查找效率。

源码解析

构造函数

javascript 复制代码
function SetCache(values) {
  var index = -1,
    length = values == null ? 0 : values.length;

  this.__data__ = new MapCache();
  while (++index < length) {
    this.add(values[index]);
  }
}

构造函数接收一个可选的 values 数组参数,用于初始化缓存:

  1. 首先初始化索引变量 index = -1,用于后续遍历
  2. 获取数组长度,如果 values 为 null 或 undefined,则长度为 0
  3. 创建一个新的 MapCache 实例作为内部存储(this.__data__
  4. 使用 while 循环遍历 values 数组,将每个元素添加到缓存中

这里使用 ++index 的前置递增操作,与初始值 -1 配合,确保从第一个元素(索引 0)开始处理。

示例:

javascript 复制代码
// 创建一个空的 SetCache
const emptyCache = new SetCache();
// 内部结构: __data__ = new MapCache

// 创建一个带有初始值的 SetCache
const cache = new SetCache([1, 2, 3, 3, 4]);
// 因为是集合,所以重复的 3 只会被存储一次
// 内部结构中的 MapCache 存储了键 1, 2, 3, 4,值均为 HASH_UNDEFINED

add 方法

javascript 复制代码
function setCacheAdd(value) {
  this.__data__.set(value, HASH_UNDEFINED);
  return this;
}

add 方法用于向集合中添加一个值:

  1. 调用内部 MapCache 实例的 set 方法,将 value 作为键,HASH_UNDEFINED 作为值存储
  2. 返回 this 以支持链式调用

值得注意的是,这里使用 MapCache 的 key-value 结构来实现 Set,键是我们要存储的实际值,而值则统一为 HASH_UNDEFINED。这样设计的好处是可以利用 MapCache 高效的键查找能力,无论是基本类型还是引用类型的值都能高效处理。

由于 MapCache 内部会自动处理重复键的情况(更新值而不增加新条目),这也自然实现了集合的唯一性特征,同一个值不会被添加多次。

示例:

javascript 复制代码
const cache = new SetCache();
cache.add("a");
cache.add(42);
cache.add({ id: 1 });

// 重复添加相同值不会产生效果
cache.add("a");
// 内部 MapCache 仍然只有三个键值对

has 方法

javascript 复制代码
function setCacheHas(value) {
  return this.__data__.has(value);
}

has 方法检查一个值是否在集合中:

  1. 直接调用内部 MapCache 实例的 has 方法
  2. 返回检查结果(布尔值)

这个实现非常简洁,只是将操作委托给内部的 MapCache 实例。MapCache 的 has 方法会根据键的类型(字符串、数字或引用类型)选择最优的存储结构进行查找,这确保了 SetCache 的查找操作也同样高效。

示例:

javascript 复制代码
const cache = new SetCache([1, 2, 3]);
console.log(cache.has(2)); // true
console.log(cache.has(4)); // false

const obj = { id: 5 };
cache.add(obj);
console.log(cache.has(obj)); // true
console.log(cache.has({ id: 5 })); // false,因为是不同的对象引用

原型方法绑定

javascript 复制代码
SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
SetCache.prototype.has = setCacheHas;

这段代码将函数绑定到 SetCache 的原型上:

  1. 将 setCacheAdd 函数绑定为 SetCache 原型的 add 方法
  2. 同时将 setCacheAdd 函数也绑定为 push 方法(作为 add 的别名)
  3. 将 setCacheHas 函数绑定为 has 方法

提供 push 作为 add 的别名是一个有趣的设计,让 SetCache 在某些场景下可以与数组保持类似的接口,提高了 API 的友好性和一致性。

总结

SetCache 是 Lodash 内部实现的一个高效集合数据结构,它基于 MapCache 提供了快速的元素添加和查找能力。虽然它的 API 非常简单(只有 add/push 和 has 两个方法),但在处理大量数据的去重、交集、差集等操作时扮演着关键角色。

从设计角度看,SetCache 展示了以下几个优秀的设计原则:

  1. 单一职责原则:SetCache 专注于提供集合的核心功能(添加和检查),不包含其他复杂操作
  2. 组合优于继承:通过内部使用 MapCache 实例而非继承它,SetCache 获得了更大的灵活性和封装性
  3. 接口简洁性:只提供必要的接口,简化了使用复杂度
  4. 性能优化:通过利用 MapCache 的高效键存储机制,优化了集合操作的性能

通过学习 SetCache 的实现,我们可以看到如何利用已有的数据结构构建新的抽象,以及如何为特定用例优化性能。这种将通用数据结构组合成专用工具的设计思路,在我们自己的代码中也值得借鉴。SetCache 虽小,但体现了数据结构设计的精妙之处,特别是在处理大量数据时如何权衡时间和空间复杂度。

相关推荐
samroom5 分钟前
React-Router路由跳转、传参、抽象封装以及嵌套路由
前端·react.js·前端框架
magic 24519 分钟前
移动端WEB开发之响应式布局
前端·css·html·html5
胡桃夹夹子21 分钟前
uniapp自身bug | uniapp+vue3打包后 index.html无法直接运行
javascript·uni-app·bug
fridayCodeFly22 分钟前
:class=“{ ‘addCheckstyle‘: hasError }“这是什么意思
前端·javascript·vue.js
知识分享小能手25 分钟前
CSS3学习教程,从入门到精通,CSS3 布局语法知识点及案例代码(15)
前端·css·学习·html·css3·html5·java后端开发
木木黄木木25 分钟前
使用CSS3实现炫酷的3D翻转卡片效果
前端·3d·css3
Sperains39 分钟前
响应式数组操作在Vue3和React中的差异
前端
阿黄学技术1 小时前
Spring框架核心注解(Spring,SpringMVC,SpringBoot)
前端·spring boot·spring
小璞1 小时前
1. Webpack 核心概念
前端·webpack
lee5761 小时前
用Promise实现ajax的自动重试
前端·javascript·ajax