Lodash源码阅读-ListCache

Lodash 源码阅读-ListCache

概述

ListCache 是 Lodash 内部使用的一个简单键值对缓存结构,基于数组实现。它主要用于存储少量的键值对数据,提供了高效的增删改查操作。

前置学习

  • assocIndexOf:用于在数组中查找指定键的索引位置
  • eq:用于比较两个值是否相等(使用 SameValueZero 算法)

源码实现

javascript 复制代码
/**
 * Creates a list cache object.
 *
 * @private
 * @constructor
 * @param {Array} [entries] The key-value pairs to cache.
 */
function ListCache(entries) {
  var index = -1,
    length = entries == null ? 0 : entries.length;

  this.clear();
  while (++index < length) {
    var entry = entries[index];
    this.set(entry[0], entry[1]);
  }
}

/**
 * Removes all key-value entries from the list cache.
 *
 * @private
 * @name clear
 * @memberOf ListCache
 */
function listCacheClear() {
  this.__data__ = [];
  this.size = 0;
}

/**
 * Removes `key` and its value from the list cache.
 *
 * @private
 * @name delete
 * @memberOf ListCache
 * @param {string} key The key of the value to remove.
 * @returns {boolean} Returns `true` if the entry was removed, else `false`.
 */
function listCacheDelete(key) {
  var data = this.__data__,
    index = assocIndexOf(data, key);

  if (index < 0) {
    return false;
  }
  var lastIndex = data.length - 1;
  if (index == lastIndex) {
    data.pop();
  } else {
    splice.call(data, index, 1);
  }
  --this.size;
  return true;
}

/**
 * Gets the list cache value for `key`.
 *
 * @private
 * @name get
 * @memberOf ListCache
 * @param {string} key The key of the value to get.
 * @returns {*} Returns the entry value.
 */
function listCacheGet(key) {
  var data = this.__data__,
    index = assocIndexOf(data, key);

  return index < 0 ? undefined : data[index][1];
}

/**
 * Checks if a list cache value for `key` exists.
 *
 * @private
 * @name has
 * @memberOf ListCache
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
function listCacheHas(key) {
  return assocIndexOf(this.__data__, key) > -1;
}

/**
 * Sets the list cache `key` to `value`.
 *
 * @private
 * @name set
 * @memberOf ListCache
 * @param {string} key The key of the value to set.
 * @param {*} value The value to set.
 * @returns {Object} Returns the list cache instance.
 */
function listCacheSet(key, value) {
  var data = this.__data__,
    index = assocIndexOf(data, key);

  if (index < 0) {
    ++this.size;
    data.push([key, value]);
  } else {
    data[index][1] = value;
  }
  return this;
}

// Add methods to `ListCache`.
ListCache.prototype.clear = listCacheClear;
ListCache.prototype["delete"] = listCacheDelete;
ListCache.prototype.get = listCacheGet;
ListCache.prototype.has = listCacheHas;
ListCache.prototype.set = listCacheSet;

实现思路

ListCache 是一个基于数组的简单键值对缓存实现,它的核心是使用数组存储键值对,每个键值对以 [key, value] 的形式存储在数组中。它主要提供以下操作:

  1. 构造函数:初始化缓存并可选地填充初始数据
  2. 清空操作:移除所有键值对
  3. 删除操作:移除指定键的键值对
  4. 获取操作:获取指定键的值
  5. 检查操作:检查指定键是否存在
  6. 设置操作:设置或更新指定键的值

所有这些操作依赖于 assocIndexOf 函数来查找键在数组中的位置,而 assocIndexOf 则使用 eq 函数进行键的比较。

源码解析

构造函数

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

  this.clear();
  while (++index < length) {
    var entry = entries[index];
    this.set(entry[0], entry[1]);
  }
}

构造函数接收一个可选的 entries 参数,这是一个键值对数组 [[key1, value1], [key2, value2], ...]。构造过程如下:

  1. 首先调用 this.clear() 初始化内部存储结构
  2. 如果提供了 entries,则遍历它,并依次调用 this.set() 设置每一个键值对

例如:

javascript 复制代码
// 创建一个空的 ListCache
const emptyCache = new ListCache();
// 内部结构: __data__ = [], size = 0

// 创建一个带有初始数据的 ListCache
const cache = new ListCache([
  ["key1", "value1"],
  ["key2", "value2"],
]);
// 内部结构: __data__ = [['key1', 'value1'], ['key2', 'value2']], size = 2

clear 方法

javascript 复制代码
function listCacheClear() {
  this.__data__ = [];
  this.size = 0;
}

清空方法非常简单,它将内部数据重置为空数组,并将 size 设为 0。

例如:

javascript 复制代码
cache.clear();
// 执行后: __data__ = [], size = 0

delete 方法

javascript 复制代码
function listCacheDelete(key) {
  var data = this.__data__,
    index = assocIndexOf(data, key);

  if (index < 0) {
    return false;
  }
  var lastIndex = data.length - 1;
  if (index == lastIndex) {
    data.pop();
  } else {
    splice.call(data, index, 1);
  }
  --this.size;
  return true;
}

删除方法根据键删除对应的键值对:

  1. 首先,使用 assocIndexOf 查找键在数组中的索引
  2. 如果没找到(索引为 -1),直接返回 false 表示删除失败
  3. 如果找到了且是数组的最后一个元素,使用 pop() 快速删除
  4. 如果找到了但不是最后一个元素,使用 splice() 从数组中删除该元素
  5. 最后,将 size 减 1 并返回 true 表示删除成功

这里有一个性能优化:当删除的是最后一个元素时,使用更高效的 pop() 而不是 splice()

例如:

javascript 复制代码
// 假设 cache.__data__ = [['key1', 'value1'], ['key2', 'value2']]
const result = cache.delete("key1");
// 执行后:__data__ = [['key2', 'value2']], size = 1, result = true

const notFound = cache.delete("key3");
// 执行后:__data__ 不变,size 不变,notFound = false

get 方法

javascript 复制代码
function listCacheGet(key) {
  var data = this.__data__,
    index = assocIndexOf(data, key);

  return index < 0 ? undefined : data[index][1];
}

获取方法根据键获取对应的值:

  1. 使用 assocIndexOf 查找键在数组中的索引
  2. 如果没找到,返回 undefined
  3. 如果找到了,返回对应键值对的值(即 data[index][1]

例如:

javascript 复制代码
// 假设 cache.__data__ = [['key1', 'value1'], ['key2', 'value2']]
const value = cache.get("key1");
// value = 'value1'

const missingValue = cache.get("key3");
// missingValue = undefined

has 方法

javascript 复制代码
function listCacheHas(key) {
  return assocIndexOf(this.__data__, key) > -1;
}

检查方法判断指定的键是否存在:

  1. 使用 assocIndexOf 查找键在数组中的索引
  2. 如果索引大于 -1,说明找到了,返回 true;否则返回 false

例如:

javascript 复制代码
// 假设 cache.__data__ = [['key1', 'value1'], ['key2', 'value2']]
const hasKey = cache.has("key2");
// hasKey = true

const doesNotHaveKey = cache.has("key3");
// doesNotHaveKey = false

set 方法

javascript 复制代码
function listCacheSet(key, value) {
  var data = this.__data__,
    index = assocIndexOf(data, key);

  if (index < 0) {
    ++this.size;
    data.push([key, value]);
  } else {
    data[index][1] = value;
  }
  return this;
}

设置方法用于添加新键值对或更新已有键值对:

  1. 使用 assocIndexOf 查找键在数组中的索引
  2. 如果没找到(索引为 -1):
    • 将 size 加 1
    • 将新的键值对 [key, value] 添加到数组末尾
  3. 如果找到了(索引不为 -1):
    • 更新已存在键值对的值(data[index][1]
  4. 返回 this 以支持链式调用

例如:

javascript 复制代码
// 假设 cache.__data__ = [['key1', 'value1']], size = 1
cache.set("key2", "value2");
// 执行后:__data__ = [['key1', 'value1'], ['key2', 'value2']], size = 2

cache.set("key1", "new value");
// 执行后:__data__ = [['key1', 'new value'], ['key2', 'value2']], size = 2

应用场景

ListCache 在 Lodash 中主要用作其他缓存结构的基础构建块,特别是:

  1. Stack 缓存的默认实现:当数据量小时,Stack 使用 ListCache 存储数据
  2. MapCache 的回退实现:当环境不支持 Map 时,部分 MapCache 功能通过 ListCache 实现
  3. 小数据量的键值存储:适用于数据量较小的场景

实际应用示例:

javascript 复制代码
// 实现一个简单的键值对存储
function createCache() {
  return new ListCache();
}

const cache = createCache();
cache.set("userId", 12345);
cache.set("username", "john_doe");

if (cache.has("userId")) {
  console.log("User ID:", cache.get("userId"));
}

// 清除特定用户信息
cache.delete("userId");

总结

ListCache 是一个基于数组的简单键值对缓存实现,它通过巧妙的数组操作提供了高效的键值对存储。其设计体现了以下几个特点:

  1. 简单高效:使用原生数组实现,结构简单,适合小数据量场景
  2. 完整接口:提供了完整的 CRUD 操作(增、删、改、查)
  3. 性能优化:在删除操作中针对不同情况选择最优算法

从设计原则角度看,ListCache 体现了:

  1. 单一职责原则:专注于提供键值对缓存功能
  2. 接口一致性:与其他缓存实现(如 MapCache)保持一致的接口
  3. 实现隐藏:用户不需要关心内部如何存储和查找数据

这种实现方式对我们有以下启示:

  1. 在数据量较小的场景下,简单的数组操作可能比复杂的数据结构更高效
  2. 设计数据结构时,应根据使用场景选择合适的底层实现
  3. 即使是简单的数据结构,也可以通过精心设计提供强大而一致的接口
相关推荐
天天扭码2 分钟前
前端进阶 | 面试必考—— JavaScript手写定时器
前端·javascript·面试
梦雨生生18 分钟前
拖拉拽效果加点击事件
前端·javascript·css
前端Hardy21 分钟前
HTML&CSS:全网最全的代码时钟效果
javascript·css·html
前端Hardy25 分钟前
HTML&CSS:看这里,动态背景卡片效果
javascript·css·html
前端Hardy26 分钟前
第2课:变量与数据类型——JS的“记忆盒子”
前端·javascript
前端Hardy28 分钟前
第1课:初识JavaScript——让你的网页“动”起来!
javascript
冴羽41 分钟前
SvelteKit 最新中文文档教程(23)—— CLI 使用指南
前端·javascript·svelte
jstart千语1 小时前
【SpringBoot】HttpServletRequest获取使用及失效问题(包含@Async异步执行方案)
java·前端·spring boot·后端·spring
徐小夕1 小时前
花了2个月时间,写了一款3D可视化编辑器3D-Tony
前端·javascript·react.js
凕雨1 小时前
Cesium学习笔记——dem/tif地形的分块与加载
前端·javascript·笔记·学习·arcgis·vue