Lodash源码阅读-MapCache

Lodash 源码阅读-MapCache

概述

MapCache 是 Lodash 内部使用的一个高效键值对缓存结构,它根据键的类型将数据分发到不同的存储结构中,实现了高效的数据存取。它是 Lodash 中许多缓存相关功能的基础实现。

前置学习

  • Hash:用于存储字符串和基本类型键的缓存结构
  • ListCache:用于存储不可哈希键的缓存结构
  • Map:原生 Map 对象,用于高效存储引用类型键
  • getMapData:MapCache 内部路由函数,根据键类型选择正确的存储结构
  • isKeyable:判断键是否可直接用于哈希表存储的辅助函数

源码实现

javascript 复制代码
/**
 * Creates a map cache object to store key-value pairs.
 *
 * @private
 * @constructor
 * @param {Array} [entries] The key-value pairs to cache.
 */
function MapCache(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 map.
 *
 * @private
 * @name clear
 * @memberOf MapCache
 */
function mapCacheClear() {
  this.size = 0;
  this.__data__ = {
    hash: new Hash(),
    map: new (Map || ListCache)(),
    string: new Hash(),
  };
}

/**
 * Removes `key` and its value from the map.
 *
 * @private
 * @name delete
 * @memberOf MapCache
 * @param {string} key The key of the value to remove.
 * @returns {boolean} Returns `true` if the entry was removed, else `false`.
 */
function mapCacheDelete(key) {
  var result = getMapData(this, key)["delete"](key);
  this.size -= result ? 1 : 0;
  return result;
}

/**
 * Gets the map value for `key`.
 *
 * @private
 * @name get
 * @memberOf MapCache
 * @param {string} key The key of the value to get.
 * @returns {*} Returns the entry value.
 */
function mapCacheGet(key) {
  return getMapData(this, key).get(key);
}

/**
 * Checks if a map value for `key` exists.
 *
 * @private
 * @name has
 * @memberOf MapCache
 * @param {string} key The key of the entry to check.
 * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
 */
function mapCacheHas(key) {
  return getMapData(this, key).has(key);
}

/**
 * Sets the map `key` to `value`.
 *
 * @private
 * @name set
 * @memberOf MapCache
 * @param {string} key The key of the value to set.
 * @param {*} value The value to set.
 * @returns {Object} Returns the map cache instance.
 */
function mapCacheSet(key, value) {
  var data = getMapData(this, key),
    size = data.size;

  data.set(key, value);
  this.size += data.size == size ? 0 : 1;
  return this;
}

// Add methods to `MapCache`.
MapCache.prototype.clear = mapCacheClear;
MapCache.prototype["delete"] = mapCacheDelete;
MapCache.prototype.get = mapCacheGet;
MapCache.prototype.has = mapCacheHas;
MapCache.prototype.set = mapCacheSet;

实现思路

MapCache 的核心设计理念是根据键的类型将数据分发到最适合的存储结构中,以实现最高效的存取性能。它使用三种不同的存储结构:

  1. hash: 使用 Hash 对象存储数字、布尔值、符号等基本类型键
  2. string: 使用 Hash 对象专门处理字符串类型的键
  3. map: 使用 Map 或 ListCache 处理复杂的引用类型键

通过这种分类存储的策略,MapCache 能够高效处理各种类型的键,无论是基本类型还是对象引用。所有操作都会通过 getMapData 函数路由到正确的存储结构,然后委托给对应结构的方法来完成。

源码解析

构造函数

javascript 复制代码
function MapCache(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 参数,这是一个键值对数组。它的工作流程是:

  1. 初始化索引变量 index 为 -1
  2. 获取 entries 的长度,如果 entries 为 null 或 undefined,则长度为 0
  3. 调用 this.clear() 初始化内部存储结构
  4. 遍历 entries 数组,依次调用 this.set() 设置每个键值对

例如:

javascript 复制代码
// 创建一个空的 MapCache
const emptyCache = new MapCache();
// 内部结构: __data__ = { hash: Hash实例, map: Map实例, string: Hash实例 }, size = 0

// 创建一个带有初始数据的 MapCache
const cache = new MapCache([
  ["name", "John"],
  [42, "answer"],
  [{ id: 1 }, "object"],
]);
// 键'name'存储在__data__.string中
// 键42存储在__data__.hash中
// 键{id: 1}存储在__data__.map中

clear 方法

javascript 复制代码
function mapCacheClear() {
  this.size = 0;
  this.__data__ = {
    hash: new Hash(),
    map: new (Map || ListCache)(),
    string: new Hash(),
  };
}

clear 方法重置缓存,将 size 设为 0,并为三种不同类型的键创建新的存储结构:

  1. 'hash': 一个新的 Hash 实例,用于存储基本类型键
  2. 'string': 一个新的 Hash 实例,专门存储字符串键
  3. 'map': 一个新的 Map 实例(如果环境支持),或者一个 ListCache 实例(作为降级方案)

这种分类存储的设计是 MapCache 高效处理不同类型键的核心。

注意这里的 new (Map || ListCache) 语法,它会尝试使用原生 Map,如果不可用则回退到 ListCache 实现。

delete 方法

javascript 复制代码
function mapCacheDelete(key) {
  var result = getMapData(this, key)["delete"](key);
  this.size -= result ? 1 : 0;
  return result;
}

delete 方法从缓存中移除指定键的键值对:

  1. 调用 getMapData 获取存储该键的正确存储结构
  2. 调用该存储结构的 delete 方法删除键
  3. 如果删除成功(result 为 true),则将 MapCache 的 size 减 1
  4. 返回删除操作的结果

例如:

javascript 复制代码
cache.delete("name"); // 从 string 存储中删除
cache.delete(42); // 从 hash 存储中删除
cache.delete({ id: 1 }); // 从 map 存储中删除(通过引用比较)

get 方法

javascript 复制代码
function mapCacheGet(key) {
  return getMapData(this, key).get(key);
}

get 方法获取指定键的值:

  1. 调用 getMapData 获取存储该键的正确存储结构
  2. 调用该存储结构的 get 方法并返回结果

这是一个非常简洁的实现,只是将操作委托给正确的子存储结构。

has 方法

javascript 复制代码
function mapCacheHas(key) {
  return getMapData(this, key).has(key);
}

has 方法检查缓存中是否存在指定的键:

  1. 调用 getMapData 获取存储该键的正确存储结构
  2. 调用该存储结构的 has 方法并返回结果

同样是一个简单的委托实现。

set 方法

javascript 复制代码
function mapCacheSet(key, value) {
  var data = getMapData(this, key),
    size = data.size;

  data.set(key, value);
  this.size += data.size == size ? 0 : 1;
  return this;
}

set 方法用于设置或更新键值对:

  1. 调用 getMapData 获取存储该键的正确存储结构
  2. 记录操作前该存储结构的 size
  3. 调用该存储结构的 set 方法设置键值对
  4. 更新 MapCache 的 size:如果子存储结构的 size 增加了(表示添加了新键),则 MapCache 的 size 也增加 1
  5. 返回 MapCache 实例以支持链式调用

这个实现确保了 MapCache 的 size 属性与其所有子存储结构中的键数量之和保持一致。

应用场景

MapCache 在 Lodash 内部被广泛使用,主要有以下几个场景:

  1. 高性能缓存 :用于实现 _.memoize 等缓存功能
  2. 去重操作 :用于 _.uniq_.intersection 等需要快速查找的操作
  3. 复杂对象键存储:当需要使用对象作为键时,MapCache 提供了高效的解决方案
  4. Stack 数据结构:当 Stack 需要处理大量数据时,会从 ListCache 升级到 MapCache
  5. SetCache 的实现基础:SetCache 内部使用 MapCache 存储唯一值

总结

MapCache 是 Lodash 中一个精巧的缓存实现,它通过根据键的类型选择最佳存储结构来实现高效的数据访问。它的设计体现了以下几个重要的软件设计原则:

  1. 分而治之:将不同类型的键分配到专门的存储结构中处理
  2. 适配器模式:通过 getMapData 函数将操作路由到正确的底层实现
  3. 降级策略:当环境不支持 Map 时自动降级到 ListCache
  4. 接口一致性:为所有操作提供统一的接口,隐藏内部的复杂性

从性能角度看,MapCache 的主要优势在于:

  1. 字符串和基本类型键可以直接通过哈希表 O(1) 时间复杂度访问
  2. 引用类型键通过 Map 高效存储,避免了传统对象键的字符串转换问题
  3. 通过细分存储结构,减少了每个子结构中的元素数量,提高了查找效率

MapCache 的实现给我们的启示是:在处理多样化的数据时,可以根据数据的特性选择不同的数据结构,通过组合它们来获得最佳的性能。这种根据键类型选择最佳存储结构的思想,可以应用到我们自己的缓存系统和数据结构设计中。

相关推荐
崔庆才丨静觅15 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606116 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了16 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅16 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅16 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅17 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment17 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅17 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊17 小时前
jwt介绍
前端
爱敲代码的小鱼17 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax