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

相关推荐
关山月30 分钟前
React 中的 SSR 深度探讨
前端
yzhSWJ1 小时前
vue设置自定义logo跟标题
前端·javascript·vue.js
江沉晚呤时1 小时前
深入解析 C# 中的装饰器模式(Decorator Pattern)
java·开发语言·javascript·jvm·microsoft·.netcore
vvilkim2 小时前
Vue.js 中的 Tree Shaking:优化你的应用性能
前端·javascript·vue.js
杨超越luckly2 小时前
HTML应用指南:利用GET请求获取猫眼电影日票房信息——以哪吒2为例
前端·数据挖掘·数据分析·html·猫眼
Front_Yue2 小时前
Three.js中的加载器与资源管理:构建丰富3D场景的关键
javascript·3d·three.js
狼性书生2 小时前
uniapp 实现的下拉菜单组件
前端·uni-app·vue·组件·插件
浪裡遊2 小时前
uniapp中的vue组件与组件使用差异
前端·vue.js·uni-app
努力的飛杨2 小时前
学习记录-js进阶-性能优化
开发语言·javascript·学习
风无雨2 小时前
react 中 key 的使用
前端·react.js·前端框架