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. 即使是简单的数据结构,也可以通过精心设计提供强大而一致的接口
相关推荐
二川bro18 分钟前
Vue3 核心特性解析:Suspense 与 Teleport 原理深度剖析
前端
不断努力的根号七21 分钟前
electron框架(1.0)认识electron和基础创建
前端·javascript·electron
fridayCodeFly21 分钟前
Vue3.如何把一个对象内的键值变成响应式变量
前端·javascript·vue.js
rookie fish22 分钟前
如何在electron中注册快捷键?
前端·javascript·electron
yqcoder26 分钟前
前端 FormData 详解
javascript·html5
hikktn28 分钟前
【开源宝藏】30天学会CSS - DAY3 第三课 滑动文本+变色
前端·css
eli96029 分钟前
node-ddk, electron 组件, 操作窗口
前端·javascript·electron·node.js
你们瞎搞36 分钟前
Cesium三维地图和leaflet二维地图卷帘分屏联动
前端·javascript·vue.js
护国神蛙2 小时前
auto-i18n-translation-plugins 全自动国际化插件——常见问题汇总
前端·javascript·开源
高hongyuan2 小时前
Nginx 代理访问一个 Web 界面时缺少内容
运维·前端·nginx